This changeset fixes a huge pile of problems, as indicated in the error log of the Doxygen run after merging all the recent Doxygen improvements unfortunately, auto-linking does still not work at various places. There is no clear indication what might be the problem. Possibly the rather unstable Sqlite support in this Doxygen version is the cause. Anyway, needs to be investigated further.
456 lines
13 KiB
C++
456 lines
13 KiB
C++
/*
|
|
VisitingTool(Concept) - concept draft of a Visitor library implementation
|
|
|
|
Copyright (C) Lumiera.org
|
|
2008, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
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 visitingtool-concept.cpp
|
|
** While laying the foundations for Session and Builder, Ichthyo came across
|
|
** the necessity to create a custom implementation of the Visitor Pattern
|
|
** optimally suited for Lumiera's needs. This implementation file was used
|
|
** for the drafting process and is self-contained. The final solution was
|
|
** then extracted later as library implementation into visitor.hpp
|
|
**
|
|
** Basic considerations
|
|
** <ul><li>cyclic dependencies should be avoided or at least restricted
|
|
** to some library related place. The responsibilities for
|
|
** user code should be as small as possible.</li>
|
|
** <li>Visitor is about <i>double dispatch</i>, thus we can't avoid
|
|
** using some table lookup implementation, and we can't avoid using
|
|
** some of the cooperating classes' vtables. Besides that, the
|
|
** implementation should not be too wasteful...</li>
|
|
** <li>individual Visiting Tool implementation classes should be able
|
|
** to opt in or opt out on implementing functions treating some of
|
|
** the visitable subclasses.</li>
|
|
** <li>there should be a safe fallback mechanism backed by the
|
|
** visitable object's hierarchy relations. If some new class declares
|
|
** to be visitable, existing Visiting Tools not yet treating this new
|
|
** visitable type should fall back rather to the next best match up the
|
|
** hierarchy, instead of invoking some almost abstract base class</li>
|
|
** </ul>
|
|
**
|
|
** @see visitor.hpp the final lib implementation
|
|
** @see visitingtooltest.cpp test cases using our lib implementation
|
|
** @see BuilderTool one especially important instantiation
|
|
**
|
|
*/
|
|
|
|
|
|
#include "lib/test/run.hpp"
|
|
#include "lib/format-cout.hpp"
|
|
#include "lib/format-string.hpp"
|
|
#include "lib/depend.hpp"
|
|
|
|
#include <vector>
|
|
|
|
using util::_Fmt;
|
|
using std::string;
|
|
|
|
|
|
namespace lumiera {
|
|
namespace visitor_concept_draft {
|
|
|
|
// ================================================================== Library ====
|
|
|
|
|
|
|
|
template<class TOOL> class Tag;
|
|
|
|
|
|
template<class TOOL, class TOOLImpl>
|
|
struct TagTypeRegistry
|
|
{
|
|
static Tag<TOOL> tag;
|
|
};
|
|
|
|
|
|
template<class TOOL>
|
|
class Tag
|
|
{
|
|
size_t tagID;
|
|
static size_t lastRegisteredID;
|
|
|
|
public:
|
|
Tag() : tagID(0) { }
|
|
operator size_t() const { return tagID; }
|
|
|
|
|
|
template<class TOOLImpl>
|
|
static Tag&
|
|
get (TOOLImpl* const =0) // param used to pass type info
|
|
{
|
|
// we have a race condition here...
|
|
Tag& t = TagTypeRegistry<TOOL,TOOLImpl>::tag;
|
|
if (!t)
|
|
t.tagID = ++lastRegisteredID;
|
|
return t;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/** storage for the Tag registry for each concrete tool */
|
|
template<class TOOL, class TOOLImpl>
|
|
Tag<TOOL> TagTypeRegistry<TOOL,TOOLImpl>::tag;
|
|
|
|
template<class TOOL>
|
|
size_t Tag<TOOL>::lastRegisteredID (0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Marker interface "visiting tool" */
|
|
template<typename RET>
|
|
class Tool
|
|
{
|
|
public:
|
|
typedef RET ReturnType;
|
|
typedef Tool<RET> ToolBase; ///< for templating the Tag and Dispatcher
|
|
|
|
virtual ~Tool() { }; ///< use RTTI for all visiting tools
|
|
|
|
/** allows discovery of the concrete Tool type when dispatching a
|
|
* visitor call. Can be implemented by inheriting from ToolType */
|
|
virtual Tag<ToolBase> getTag() =0;
|
|
};
|
|
|
|
|
|
/** Mixin for attaching a type tag to the concrete tool implementation */
|
|
template<class TOOLImpl, class BASE =Tool<void>>
|
|
class ToolType
|
|
: public BASE
|
|
{
|
|
typedef typename BASE::ToolBase ToolBase;
|
|
|
|
public:
|
|
virtual Tag<ToolBase>
|
|
getTag()
|
|
{
|
|
TOOLImpl* typeKey = 0;
|
|
return Tag<ToolBase>::get (typeKey);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
* For each possible call entry point via some subclass of the visitable hierarchy,
|
|
* we maintain a dispatcher table to keep track of all concrete tool implementations
|
|
* able to receive and process calls on objects of this subclass.
|
|
*/
|
|
template<class TAR, class TOOL>
|
|
class Dispatcher
|
|
{
|
|
typedef typename TOOL::ReturnType ReturnType;
|
|
|
|
/** generator for Trampoline functions,
|
|
* used to dispatch calls down to the
|
|
* right "treat"-Function on the correct
|
|
* concrete tool implementation class
|
|
*/
|
|
template<class TOOLImpl>
|
|
static ReturnType
|
|
callTrampoline (TAR& obj, TOOL& tool)
|
|
{
|
|
// cast down to real implementation type
|
|
CHECK (INSTANCEOF (TOOLImpl, &tool));
|
|
TOOLImpl& toolObj = static_cast<TOOLImpl&> (tool);
|
|
|
|
// trigger (compile time) overload resolution
|
|
// based on concrete type, then dispatch the call.
|
|
// Note this may cause obj to be upcasted.
|
|
return toolObj.treat (obj);
|
|
}
|
|
|
|
typedef ReturnType (*Trampoline) (TAR&, TOOL& );
|
|
|
|
|
|
/** custom VTable for storing the Trampoline pointers */
|
|
std::vector<Trampoline> table_;
|
|
|
|
|
|
inline bool
|
|
is_known (size_t id)
|
|
{
|
|
return id<=table_.size() && table_[id-1];
|
|
}
|
|
|
|
inline void
|
|
storePtr (size_t id, Trampoline func)
|
|
{
|
|
// lacks error- and concurrency handling....
|
|
if (id>table_.size())
|
|
table_.resize (id);
|
|
table_[id-1] = func;
|
|
}
|
|
|
|
inline Trampoline
|
|
storedTrampoline (size_t id)
|
|
{
|
|
if (id<=table_.size() && table_[id-1])
|
|
return table_[id-1];
|
|
else
|
|
return &errorHandler;
|
|
}
|
|
|
|
static ReturnType
|
|
errorHandler (TAR&, TOOL&)
|
|
{
|
|
cout << "Error Handler: unregistered combination of (Tool, TargetObject) invoked!\n";
|
|
}
|
|
|
|
|
|
public:
|
|
static lib::Depend<Dispatcher<TAR,TOOL>> instance;
|
|
|
|
inline ReturnType
|
|
forwardCall (TAR& target, TOOL& tool)
|
|
{
|
|
// get concrete type via tool's VTable
|
|
Tag<TOOL> index = tool.getTag();
|
|
return (*storedTrampoline(index)) (target, tool);
|
|
}
|
|
|
|
template<class TOOLImpl>
|
|
inline void
|
|
enrol (TOOLImpl* typeKey)
|
|
{
|
|
Tag<TOOL>& index = Tag<TOOL>::get (typeKey);
|
|
if (is_known (index))
|
|
return;
|
|
else
|
|
{
|
|
Trampoline func = &callTrampoline<TOOLImpl>;
|
|
storePtr (index, func);
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
/** storage for the dispatcher table(s) */
|
|
template<class TAR, class TOOL>
|
|
lib::Depend<Dispatcher<TAR,TOOL> > Dispatcher<TAR,TOOL>::instance;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* any concrete visiting tool implementation has to inherit from
|
|
* this class for each kind of calls it wants to get dispatched,
|
|
* Allowing us to record the type information.
|
|
*/
|
|
template<class TAR, class TOOLImpl, class BASE=Tool<void> >
|
|
class Applicable
|
|
{
|
|
typedef typename BASE::ReturnType Ret;
|
|
typedef typename BASE::ToolBase ToolBase;
|
|
|
|
protected:
|
|
Applicable()
|
|
{
|
|
TOOLImpl* typeKey = 0;
|
|
Dispatcher<TAR,ToolBase>::instance().enrol (typeKey);
|
|
}
|
|
|
|
virtual ~Applicable () {}
|
|
|
|
public:
|
|
// we could enforce the definition of treat()-functions by:
|
|
//
|
|
// virtual Ret treat (TAR& target) = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** Marker interface "visitable object".
|
|
*/
|
|
template
|
|
< class TOOL = Tool<void>
|
|
>
|
|
class Visitable
|
|
{
|
|
protected:
|
|
virtual ~Visitable() { };
|
|
|
|
/// @note may differ from TOOL
|
|
typedef typename TOOL::ToolBase ToolBase;
|
|
typedef typename TOOL::ReturnType ReturnType;
|
|
|
|
/** @internal used by the DEFINE_PROCESSABLE_BY macro.
|
|
* Dispatches to the actual operation on the
|
|
* "visiting tool" (visitor implementation)
|
|
* Note: creates a context templated on concrete TAR.
|
|
*/
|
|
template <class TAR>
|
|
static inline ReturnType
|
|
dispatchOp (TAR& target, TOOL& tool)
|
|
{
|
|
return Dispatcher<TAR,ToolBase>::instance().forwardCall (target,tool);
|
|
}
|
|
|
|
public:
|
|
/** to be defined by the DEFINE_PROCESSABLE_BY macro
|
|
* in all classes wanting to be treated by some tool */
|
|
virtual ReturnType apply (TOOL&) = 0;
|
|
};
|
|
|
|
|
|
/** mark a Visitable subclass as actually treatable
|
|
* by some "visiting tool". Defines the apply-function,
|
|
* which is the actual access point to invoke the visiting
|
|
*/
|
|
#define DEFINE_PROCESSABLE_BY(TOOL) \
|
|
virtual ReturnType apply (TOOL& tool) \
|
|
{ return dispatchOp (*this, tool); }
|
|
|
|
|
|
// =============================================================(End) Library ====
|
|
|
|
|
|
|
|
|
|
|
|
namespace test {
|
|
|
|
typedef Tool<void> VisitingTool;
|
|
|
|
class HomoSapiens : public Visitable<>
|
|
{
|
|
public:
|
|
DEFINE_PROCESSABLE_BY (VisitingTool);
|
|
};
|
|
|
|
class Boss : public HomoSapiens
|
|
{
|
|
public:
|
|
DEFINE_PROCESSABLE_BY (VisitingTool);
|
|
};
|
|
|
|
class BigBoss : public Boss
|
|
{
|
|
public:
|
|
DEFINE_PROCESSABLE_BY (VisitingTool);
|
|
};
|
|
|
|
class Leader : public Boss
|
|
{
|
|
};
|
|
|
|
class Visionary : public Leader
|
|
{
|
|
};
|
|
|
|
|
|
class VerboseVisitor
|
|
: public VisitingTool
|
|
{
|
|
protected:
|
|
void talk_to (string guy)
|
|
{
|
|
cout << _Fmt{"Hello %s, nice to meet you...\n"} % guy;
|
|
}
|
|
};
|
|
|
|
|
|
class Babbler
|
|
: public Applicable<Boss,Babbler>,
|
|
public Applicable<BigBoss,Babbler>,
|
|
public Applicable<Visionary,Babbler>,
|
|
public ToolType<Babbler, VerboseVisitor>
|
|
{
|
|
public:
|
|
void treat (Boss&) { talk_to("Boss"); }
|
|
void treat (BigBoss&) { talk_to("Big Boss"); }
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************//**
|
|
* @test build and run some common cases for developing and verifying
|
|
* ichthyo's implementation concept for the Visitor Pattern.
|
|
* Defines a hierarchy of test classes to check the following cases
|
|
* <ul><li>calling the correct visiting tool specialized function
|
|
* for given concrete hierarchy classes</li>
|
|
* <li>visiting tool not declaring to visit some class</li>
|
|
* <li>newly added and not properly declared Visitable class
|
|
* causes the dispatcher to invoke an error handler</li>
|
|
* </ul>
|
|
*/
|
|
class VisitingTool_concept : public Test
|
|
{
|
|
virtual void run(Arg)
|
|
{
|
|
known_visitor_known_class();
|
|
visitor_not_visiting_some_class();
|
|
}
|
|
|
|
void known_visitor_known_class()
|
|
{
|
|
Boss x1;
|
|
BigBoss x2;
|
|
|
|
// masquerade as HomoSapiens...
|
|
HomoSapiens& homo1 (x1);
|
|
HomoSapiens& homo2 (x2);
|
|
|
|
cout << "=== Babbler meets Boss and BigBoss ===\n";
|
|
Babbler bab;
|
|
VisitingTool& vista (bab);
|
|
homo1.apply (vista);
|
|
homo2.apply (vista);
|
|
}
|
|
|
|
void visitor_not_visiting_some_class()
|
|
{
|
|
HomoSapiens x1;
|
|
Visionary x2;
|
|
|
|
HomoSapiens& homo1 (x1);
|
|
HomoSapiens& homo2 (x2);
|
|
|
|
cout << "=== Babbler meets HomoSapiens and Visionary ===\n";
|
|
Babbler bab;
|
|
VisitingTool& vista (bab);
|
|
homo1.apply (vista); // error handler (not Applicable to HomoSapiens)
|
|
homo2.apply (vista); // treats Visionary as Boss
|
|
}
|
|
|
|
};
|
|
|
|
|
|
/** Register this test class... */
|
|
LAUNCHER (VisitingTool_concept, "unit common");
|
|
|
|
|
|
|
|
}}} // namespace lumiera::visitor_concept_draft::test
|