From dc3833f31b8a20220ddb1775e1625c016c397435 Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Fri, 19 Jun 2009 00:17:30 +0000
Subject: [PATCH] DEV-31980: extract dispatch-by-string-name logic from
 LLAresListener to new LLEventDispatcher and LLDispatchListener classes. See
 LLAresListener for example usage.

---
 indra/llcommon/CMakeLists.txt                 |   2 +
 indra/llcommon/lleventdispatcher.cpp          | 123 ++++++++++++++++++
 indra/llcommon/lleventdispatcher.h            | 108 +++++++++++++++
 indra/llmessage/llareslistener.cpp            |  46 +------
 indra/llmessage/llareslistener.h              |  14 +-
 indra/llmessage/tests/llareslistener_test.cpp |   2 +-
 6 files changed, 242 insertions(+), 53 deletions(-)
 create mode 100644 indra/llcommon/lleventdispatcher.cpp
 create mode 100644 indra/llcommon/lleventdispatcher.h

diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index e920475f546..88acb70b8a0 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -37,6 +37,7 @@ set(llcommon_SOURCE_FILES
     llerrorthread.cpp
     llevent.cpp
     lleventcoro.cpp
+    lleventdispatcher.cpp
     lleventfilter.cpp
     llevents.cpp
     llfasttimer.cpp
@@ -126,6 +127,7 @@ set(llcommon_HEADER_FILES
     llerrorthread.h
     llevent.h
     lleventcoro.h
+    lleventdispatcher.h
     lleventfilter.h
     llevents.h
     lleventemitter.h
diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp
new file mode 100644
index 00000000000..2dbd59b1560
--- /dev/null
+++ b/indra/llcommon/lleventdispatcher.cpp
@@ -0,0 +1,123 @@
+/**
+ * @file   lleventdispatcher.cpp
+ * @author Nat Goodspeed
+ * @date   2009-06-18
+ * @brief  Implementation for lleventdispatcher.
+ * 
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if LL_WINDOWS
+#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
+#endif
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "lleventdispatcher.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "llevents.h"
+#include "llerror.h"
+#include "llsdutil.h"
+
+LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
+    mDesc(desc),
+    mKey(key)
+{
+}
+
+LLEventDispatcher::~LLEventDispatcher()
+{
+}
+
+/// Register a callable by name
+void LLEventDispatcher::add(const std::string& name, const Callable& callable, const LLSD& required)
+{
+    mDispatch[name] = DispatchMap::mapped_type(callable, required);
+}
+
+void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const
+{
+    LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name
+                                 << "): " << classname << " is not a subclass "
+                                 << "of LLEventDispatcher" << LL_ENDL;
+}
+
+/// Unregister a callable
+bool LLEventDispatcher::remove(const std::string& name)
+{
+    DispatchMap::iterator found = mDispatch.find(name);
+    if (found == mDispatch.end())
+    {
+        return false;
+    }
+    mDispatch.erase(found);
+    return true;
+}
+
+/// Call a registered callable with an explicitly-specified name. If no
+/// such callable exists, die with LL_ERRS.
+void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const
+{
+    if (! attemptCall(name, event))
+    {
+        LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name
+                                     << "' not found" << LL_ENDL;
+    }
+}
+
+/// Extract the @a key value from the incoming @a event, and call the
+/// callable whose name is specified by that map @a key. If no such
+/// callable exists, die with LL_ERRS.
+void LLEventDispatcher::operator()(const LLSD& event) const
+{
+    // This could/should be implemented in terms of the two-arg overload.
+    // However -- we can produce a more informative error message.
+    std::string name(event[mKey]);
+    if (! attemptCall(name, event))
+    {
+        LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey
+                                     << " value '" << name << "'" << LL_ENDL;
+    }
+}
+
+bool LLEventDispatcher::attemptCall(const std::string& name, const LLSD& event) const
+{
+    DispatchMap::const_iterator found = mDispatch.find(name);
+    if (found == mDispatch.end())
+    {
+        // The reason we only return false, leaving it up to our caller to die
+        // with LL_ERRS, is that different callers have different amounts of
+        // available information.
+        return false;
+    }
+    // Found the name, so it's plausible to even attempt the call. But first,
+    // validate the syntax of the event itself.
+    std::string mismatch(llsd_matches(found->second.second, event));
+    if (! mismatch.empty())
+    {
+        LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ") calling '" << name
+                                     << "': bad request: " << mismatch << LL_ENDL;
+    }
+    // Event syntax looks good, go for it!
+    (found->second.first)(event);
+    return true;                    // tell caller we were able to call
+}
+
+LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key):
+    LLEventDispatcher(pumpname, key),
+    mPump(pumpname, true),          // allow tweaking for uniqueness
+    mBoundListener(mPump.listen("self", boost::bind(&LLDispatchListener::process, this, _1)))
+{
+}
+
+bool LLDispatchListener::process(const LLSD& event)
+{
+    (*this)(event);
+    return false;
+}
diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h
new file mode 100644
index 00000000000..d75055fd6f2
--- /dev/null
+++ b/indra/llcommon/lleventdispatcher.h
@@ -0,0 +1,108 @@
+/**
+ * @file   lleventdispatcher.h
+ * @author Nat Goodspeed
+ * @date   2009-06-18
+ * @brief  Central mechanism for dispatching events by string name. This is
+ *         useful when you have a single LLEventPump listener on which you can
+ *         request different operations, vs. instantiating a different
+ *         LLEventPump for each such operation.
+ * 
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLEVENTDISPATCHER_H)
+#define LL_LLEVENTDISPATCHER_H
+
+#include <string>
+#include <map>
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <typeinfo>
+#include "llevents.h"
+
+class LLSD;
+
+/**
+ * Given an LLSD map, examine a string-valued key and call a corresponding
+ * callable. This class is designed to be contained by an LLEventPump
+ * listener class that will register some of its own methods, though any
+ * callable can be used.
+ */
+class LLEventDispatcher
+{
+public:
+    LLEventDispatcher(const std::string& desc, const std::string& key);
+    virtual ~LLEventDispatcher();
+
+    /// Accept any C++ callable, typically a boost::bind() expression
+    typedef boost::function<void(const LLSD&)> Callable;
+
+    /// Register a @a callable by @a name. The optional @a required parameter
+    /// is used to validate the structure of each incoming event (see
+    /// llsd_matches()).
+    void add(const std::string& name, const Callable& callable, const LLSD& required=LLSD());
+
+    /// Special case: a subclass of this class can register a @a method
+    /// without explicitly specifying the <tt>boost::bind()</tt> expression.
+    /// The optional @a required parameter is used to validate the structure
+    /// of each incoming event (see llsd_matches()).
+    template <class CLASS>
+    void add(const std::string& name, void (CLASS::*method)(const LLSD&),
+             const LLSD& required=LLSD())
+    {
+        CLASS* downcast = dynamic_cast<CLASS*>(this);
+        if (! downcast)
+        {
+            addFail(name, typeid(CLASS).name());
+        }
+        else
+        {
+            add(name, boost::bind(method, downcast, _1), required);
+        }
+    }
+
+    /// Unregister a callable
+    bool remove(const std::string& name);
+
+    /// Call a registered callable with an explicitly-specified name. If no
+    /// such callable exists, die with LL_ERRS. If the @a event fails to match
+    /// the @a required prototype specified at add() time, die with LL_ERRS.
+    void operator()(const std::string& name, const LLSD& event) const;
+
+    /// Extract the @a key value from the incoming @a event, and call the
+    /// callable whose name is specified by that map @a key. If no such
+    /// callable exists, die with LL_ERRS. If the @a event fails to match the
+    /// @a required prototype specified at add() time, die with LL_ERRS.
+    void operator()(const LLSD& event) const;
+
+private:
+    void addFail(const std::string& name, const std::string& classname) const;
+    /// try to dispatch, return @c true if success
+    bool attemptCall(const std::string& name, const LLSD& event) const;
+
+    std::string mDesc, mKey;
+    typedef std::map<std::string, std::pair<Callable, LLSD> > DispatchMap;
+    DispatchMap mDispatch;
+};
+
+/**
+ * Bundle an LLEventPump and a listener with an LLEventDispatcher. A class
+ * that contains (or derives from) LLDispatchListener need only specify the
+ * LLEventPump name and dispatch key, and add() its methods. Incoming events
+ * will automatically be dispatched.
+ */
+class LLDispatchListener: public LLEventDispatcher
+{
+public:
+    LLDispatchListener(const std::string& pumpname, const std::string& key);
+
+private:
+    bool process(const LLSD& event);
+
+    LLEventStream mPump;
+    LLTempBoundListener mBoundListener;
+};
+
+#endif /* ! defined(LL_LLEVENTDISPATCHER_H) */
diff --git a/indra/llmessage/llareslistener.cpp b/indra/llmessage/llareslistener.cpp
index 4bf375069d6..a8beb8cbde3 100644
--- a/indra/llmessage/llareslistener.cpp
+++ b/indra/llmessage/llareslistener.cpp
@@ -9,10 +9,6 @@
  * $/LicenseInfo$
  */
 
-#if LL_WINDOWS
-#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
-#endif
-
 // Precompiled header
 #include "linden_common.h"
 // associated header
@@ -27,35 +23,13 @@
 #include "llsdutil.h"
 
 LLAresListener::LLAresListener(const std::string& pumpname, LLAres* llares):
-    mAres(llares),
-    mBoundListener(LLEventPumps::instance().
-                   obtain(pumpname).
-                   listen("LLAresListener", boost::bind(&LLAresListener::process, this, _1)))
-{
-    // Insert an entry into our mDispatch map for every method we want to be
-    // able to invoke via this event API.
-    mDispatch["rewriteURI"] = boost::bind(&LLAresListener::rewriteURI, this, _1);
-}
-
-bool LLAresListener::process(const LLSD& command)
+    LLDispatchListener(pumpname, "op"),
+    mAres(llares)
 {
-    const std::string op(command["op"]);
-    // Look up the requested operation.
-    DispatchMap::const_iterator found = mDispatch.find(op);
-    if (found == mDispatch.end())
-    {
-        // There's no feedback other than our own reply. If somebody asks
-        // for an operation that's not supported (perhaps because of a
-        // typo?), unless we holler loudly, the request will be silently
-        // ignored. Throwing a tantrum on such errors will hopefully make
-        // this product more robust.
-        LL_ERRS("LLAresListener") << "Unsupported request " << op << LL_ENDL;
-        return false;
-    }
-    // Having found the operation, call it.
-    found->second(command);
-    // Conventional LLEventPump listener return
-    return false;
+    // add() every method we want to be able to invoke via this event API.
+    // Optional third parameter validates expected LLSD request structure.
+    add("rewriteURI", &LLAresListener::rewriteURI,
+        LLSD().insert("uri", LLSD()).insert("reply", LLSD()));
 }
 
 /// This UriRewriteResponder subclass packages returned URIs as an LLSD
@@ -97,13 +71,5 @@ class UriRewriteResponder: public LLAres::UriRewriteResponder
 
 void LLAresListener::rewriteURI(const LLSD& data)
 {
-    static LLSD required(LLSD().insert("uri", LLSD()).insert("reply", LLSD()));
-    // Validate that the request is well-formed
-    std::string mismatch(llsd_matches(required, data));
-    if (! mismatch.empty())
-    {
-        LL_ERRS("LLAresListener") << "bad rewriteURI request: " << mismatch << LL_ENDL;
-    }
-    // Looks as though we have what we need; issue the request
     mAres->rewriteURI(data["uri"], new UriRewriteResponder(data));
 }
diff --git a/indra/llmessage/llareslistener.h b/indra/llmessage/llareslistener.h
index 8835440c5d3..bf093b3d3d7 100644
--- a/indra/llmessage/llareslistener.h
+++ b/indra/llmessage/llareslistener.h
@@ -14,33 +14,23 @@
 #if ! defined(LL_LLARESLISTENER_H)
 #define LL_LLARESLISTENER_H
 
-#include <string>
-#include <map>
-#include <boost/function.hpp>
-#include "llevents.h"
+#include "lleventdispatcher.h"
 
 class LLAres;
 class LLSD;
 
 /// Listen on an LLEventPump with specified name for LLAres request events.
-class LLAresListener
+class LLAresListener: public LLDispatchListener
 {
 public:
     /// Specify the pump name on which to listen, and bind the LLAres instance
     /// to use (e.g. gAres)
     LLAresListener(const std::string& pumpname, LLAres* llares);
 
-    /// Handle request events on the event pump specified at construction time
-    bool process(const LLSD& command);
-
 private:
     /// command["op"] == "rewriteURI" 
     void rewriteURI(const LLSD& data);
 
-    typedef boost::function<void(const LLSD&)> Callable;
-    typedef std::map<std::string, Callable> DispatchMap;
-    DispatchMap mDispatch;
-    LLTempBoundListener mBoundListener;
     LLAres* mAres;
 };
 
diff --git a/indra/llmessage/tests/llareslistener_test.cpp b/indra/llmessage/tests/llareslistener_test.cpp
index 215a3806f8f..ac4886ccf4f 100644
--- a/indra/llmessage/tests/llareslistener_test.cpp
+++ b/indra/llmessage/tests/llareslistener_test.cpp
@@ -130,7 +130,7 @@ namespace tut
         {
             threw = e.what();
         }
-        ensure_contains("LLAresListener bad op", threw, "Unsupported");
+        ensure_contains("LLAresListener bad op", threw, "bad");
     }
 
     template<> template<>
-- 
GitLab