/indra/llcommon/lleventdispatcher.cpp
https://bitbucket.org/lindenlab/viewer-beta/ · C++ · 672 lines · 423 code · 56 blank · 193 comment · 57 complexity · fd77be389a54e4d834a3278638616a20 MD5 · raw file
- /**
- * @file lleventdispatcher.cpp
- * @author Nat Goodspeed
- * @date 2009-06-18
- * @brief Implementation for lleventdispatcher.
- *
- * $LicenseInfo:firstyear=2009&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/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"
- #include "stringize.h"
- #include <memory> // std::auto_ptr
- /*****************************************************************************
- * LLSDArgsSource
- *****************************************************************************/
- /**
- * Store an LLSD array, producing its elements one at a time. Die with LL_ERRS
- * if the consumer requests more elements than the array contains.
- */
- class LL_COMMON_API LLSDArgsSource
- {
- public:
- LLSDArgsSource(const std::string function, const LLSD& args);
- ~LLSDArgsSource();
- LLSD next();
- void done() const;
- private:
- std::string _function;
- LLSD _args;
- LLSD::Integer _index;
- };
- LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args):
- _function(function),
- _args(args),
- _index(0)
- {
- if (! (_args.isUndefined() || _args.isArray()))
- {
- LL_ERRS("LLSDArgsSource") << _function << " needs an args array instead of "
- << _args << LL_ENDL;
- }
- }
- LLSDArgsSource::~LLSDArgsSource()
- {
- done();
- }
- LLSD LLSDArgsSource::next()
- {
- if (_index >= _args.size())
- {
- LL_ERRS("LLSDArgsSource") << _function << " requires more arguments than the "
- << _args.size() << " provided: " << _args << LL_ENDL;
- }
- return _args[_index++];
- }
- void LLSDArgsSource::done() const
- {
- if (_index < _args.size())
- {
- LL_WARNS("LLSDArgsSource") << _function << " only consumed " << _index
- << " of the " << _args.size() << " arguments provided: "
- << _args << LL_ENDL;
- }
- }
- /*****************************************************************************
- * LLSDArgsMapper
- *****************************************************************************/
- /**
- * From a formal parameters description and a map of arguments, construct an
- * arguments array.
- *
- * That is, given:
- * - an LLSD array of length n containing parameter-name strings,
- * corresponding to the arguments of a function of interest
- * - an LLSD collection specifying default parameter values, either:
- * - an LLSD array of length m <= n, matching the rightmost m params, or
- * - an LLSD map explicitly stating default name=value pairs
- * - an LLSD map of parameter names and actual values for a particular
- * function call
- * construct an LLSD array of actual argument values for this function call.
- *
- * The parameter-names array and the defaults collection describe the function
- * being called. The map might vary with every call, providing argument values
- * for the described parameters.
- *
- * The array of parameter names must match the number of parameters expected
- * by the function of interest.
- *
- * If you pass a map of default parameter values, it provides default values
- * as you might expect. It is an error to specify a default value for a name
- * not listed in the parameters array.
- *
- * If you pass an array of default parameter values, it is mapped to the
- * rightmost m of the n parameter names. It is an error if the default-values
- * array is longer than the parameter-names array. Consider the following
- * parameter names: ["a", "b", "c", "d"].
- *
- * - An empty array of default values (or an isUndefined() value) asserts that
- * every one of the above parameter names is required.
- * - An array of four default values [1, 2, 3, 4] asserts that every one of
- * the above parameters is optional. If the current parameter map is empty,
- * they will be passed to the function as [1, 2, 3, 4].
- * - An array of two default values [11, 12] asserts that parameters "a" and
- * "b" are required, while "c" and "d" are optional, having default values
- * "c"=11 and "d"=12.
- *
- * The arguments array is constructed as follows:
- *
- * - Arguments-map keys not found in the parameter-names array are ignored.
- * - Entries from the map provide values for an improper subset of the
- * parameters named in the parameter-names array. This results in a
- * tentative values array with "holes." (size of map) + (number of holes) =
- * (size of names array)
- * - Holes are filled with the default values.
- * - Any remaining holes constitute an error.
- */
- class LL_COMMON_API LLSDArgsMapper
- {
- public:
- /// Accept description of function: function name, param names, param
- /// default values
- LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults);
- /// Given arguments map, return LLSD::Array of parameter values, or LL_ERRS.
- LLSD map(const LLSD& argsmap) const;
- private:
- static std::string formatlist(const LLSD&);
- // The function-name string is purely descriptive. We want error messages
- // to be able to indicate which function's LLSDArgsMapper has the problem.
- std::string _function;
- // Store the names array pretty much as given.
- LLSD _names;
- // Though we're handed an array of name strings, it's more useful to us to
- // store it as a map from name string to position index. Of course that's
- // easy to generate from the incoming names array, but why do it more than
- // once?
- typedef std::map<LLSD::String, LLSD::Integer> IndexMap;
- IndexMap _indexes;
- // Generated array of default values, aligned with the array of param names.
- LLSD _defaults;
- // Indicate whether we have a default value for each param.
- typedef std::vector<char> FilledVector;
- FilledVector _has_dft;
- };
- LLSDArgsMapper::LLSDArgsMapper(const std::string& function,
- const LLSD& names, const LLSD& defaults):
- _function(function),
- _names(names),
- _has_dft(names.size())
- {
- if (! (_names.isUndefined() || _names.isArray()))
- {
- LL_ERRS("LLSDArgsMapper") << function << " names must be an array, not " << names << LL_ENDL;
- }
- LLSD::Integer nparams(_names.size());
- // From _names generate _indexes.
- for (LLSD::Integer ni = 0, nend = _names.size(); ni < nend; ++ni)
- {
- _indexes[_names[ni]] = ni;
- }
- // Presize _defaults() array so we don't have to resize it more than once.
- // All entries are initialized to LLSD(); but since _has_dft is still all
- // 0, they're all "holes" for now.
- if (nparams)
- {
- _defaults[nparams - 1] = LLSD();
- }
- if (defaults.isUndefined() || defaults.isArray())
- {
- LLSD::Integer ndefaults = defaults.size();
- // defaults is a (possibly empty) array. Right-align it with names.
- if (ndefaults > nparams)
- {
- LL_ERRS("LLSDArgsMapper") << function << " names array " << names
- << " shorter than defaults array " << defaults << LL_ENDL;
- }
- // Offset by which we slide defaults array right to right-align with
- // _names array
- LLSD::Integer offset = nparams - ndefaults;
- // Fill rightmost _defaults entries from defaults, and mark them as
- // filled
- for (LLSD::Integer i = 0, iend = ndefaults; i < iend; ++i)
- {
- _defaults[i + offset] = defaults[i];
- _has_dft[i + offset] = 1;
- }
- }
- else if (defaults.isMap())
- {
- // defaults is a map. Use it to populate the _defaults array.
- LLSD bogus;
- for (LLSD::map_const_iterator mi(defaults.beginMap()), mend(defaults.endMap());
- mi != mend; ++mi)
- {
- IndexMap::const_iterator ixit(_indexes.find(mi->first));
- if (ixit == _indexes.end())
- {
- bogus.append(mi->first);
- continue;
- }
- LLSD::Integer pos = ixit->second;
- // Store default value at that position in the _defaults array.
- _defaults[pos] = mi->second;
- // Don't forget to record the fact that we've filled this
- // position.
- _has_dft[pos] = 1;
- }
- if (bogus.size())
- {
- LL_ERRS("LLSDArgsMapper") << function << " defaults specified for nonexistent params "
- << formatlist(bogus) << LL_ENDL;
- }
- }
- else
- {
- LL_ERRS("LLSDArgsMapper") << function << " defaults must be a map or an array, not "
- << defaults << LL_ENDL;
- }
- }
- LLSD LLSDArgsMapper::map(const LLSD& argsmap) const
- {
- if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray()))
- {
- LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map or array, not "
- << argsmap << LL_ENDL;
- }
- // Initialize the args array. Indexing a non-const LLSD array grows it
- // to appropriate size, but we don't want to resize this one on each
- // new operation. Just make it as big as we need before we start
- // stuffing values into it.
- LLSD args(LLSD::emptyArray());
- if (_defaults.size() == 0)
- {
- // If this function requires no arguments, fast exit. (Don't try to
- // assign to args[-1].)
- return args;
- }
- args[_defaults.size() - 1] = LLSD();
- // Get a vector of chars to indicate holes. It's tempting to just scan
- // for LLSD::isUndefined() values after filling the args array from
- // the map, but it's plausible for caller to explicitly pass
- // isUndefined() as the value of some parameter name. That's legal
- // since isUndefined() has well-defined conversions (default value)
- // for LLSD data types. So use a whole separate array for detecting
- // holes. (Avoid std::vector<bool> which is known to be odd -- can we
- // iterate?)
- FilledVector filled(args.size());
- if (argsmap.isArray())
- {
- // Fill args from array. If there are too many args in passed array,
- // ignore the rest.
- LLSD::Integer size(argsmap.size());
- if (size > args.size())
- {
- // We don't just use std::min() because we want to sneak in this
- // warning if caller passes too many args.
- LL_WARNS("LLSDArgsMapper") << _function << " needs " << args.size()
- << " params, ignoring last " << (size - args.size())
- << " of passed " << size << ": " << argsmap << LL_ENDL;
- size = args.size();
- }
- for (LLSD::Integer i(0); i < size; ++i)
- {
- // Copy the actual argument from argsmap
- args[i] = argsmap[i];
- // Note that it's been filled
- filled[i] = 1;
- }
- }
- else
- {
- // argsmap is in fact a map. Walk the map.
- for (LLSD::map_const_iterator mi(argsmap.beginMap()), mend(argsmap.endMap());
- mi != mend; ++mi)
- {
- // mi->first is a parameter-name string, with mi->second its
- // value. Look up the name's position index in _indexes.
- IndexMap::const_iterator ixit(_indexes.find(mi->first));
- if (ixit == _indexes.end())
- {
- // Allow for a map containing more params than were passed in
- // our names array. Caller typically receives a map containing
- // the function name, cruft such as reqid, etc. Ignore keys
- // not defined in _indexes.
- LL_DEBUGS("LLSDArgsMapper") << _function << " ignoring "
- << mi->first << "=" << mi->second << LL_ENDL;
- continue;
- }
- LLSD::Integer pos = ixit->second;
- // Store the value at that position in the args array.
- args[pos] = mi->second;
- // Don't forget to record the fact that we've filled this
- // position.
- filled[pos] = 1;
- }
- }
- // Fill any remaining holes from _defaults.
- LLSD unfilled(LLSD::emptyArray());
- for (LLSD::Integer i = 0, iend = args.size(); i < iend; ++i)
- {
- if (! filled[i])
- {
- // If there's no default value for this parameter, that's an
- // error.
- if (! _has_dft[i])
- {
- unfilled.append(_names[i]);
- }
- else
- {
- args[i] = _defaults[i];
- }
- }
- }
- // If any required args -- args without defaults -- were left unfilled
- // by argsmap, that's a problem.
- if (unfilled.size())
- {
- LL_ERRS("LLSDArgsMapper") << _function << " missing required arguments "
- << formatlist(unfilled) << " from " << argsmap << LL_ENDL;
- }
- // done
- return args;
- }
- std::string LLSDArgsMapper::formatlist(const LLSD& list)
- {
- std::ostringstream out;
- const char* delim = "";
- for (LLSD::array_const_iterator li(list.beginArray()), lend(list.endArray());
- li != lend; ++li)
- {
- out << delim << li->asString();
- delim = ", ";
- }
- return out.str();
- }
- LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key):
- mDesc(desc),
- mKey(key)
- {
- }
- LLEventDispatcher::~LLEventDispatcher()
- {
- }
- /**
- * DispatchEntry subclass used for callables accepting(const LLSD&)
- */
- struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchEntry
- {
- LLSDDispatchEntry(const std::string& desc, const Callable& func, const LLSD& required):
- DispatchEntry(desc),
- mFunc(func),
- mRequired(required)
- {}
- Callable mFunc;
- LLSD mRequired;
- virtual void call(const std::string& desc, const LLSD& event) const
- {
- // Validate the syntax of the event itself.
- std::string mismatch(llsd_matches(mRequired, event));
- if (! mismatch.empty())
- {
- LL_ERRS("LLEventDispatcher") << desc << ": bad request: " << mismatch << LL_ENDL;
- }
- // Event syntax looks good, go for it!
- mFunc(event);
- }
- virtual LLSD addMetadata(LLSD meta) const
- {
- meta["required"] = mRequired;
- return meta;
- }
- };
- /**
- * DispatchEntry subclass for passing LLSD to functions accepting
- * arbitrary argument types (convertible via LLSDParam)
- */
- struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry
- {
- ParamsDispatchEntry(const std::string& desc, const invoker_function& func):
- DispatchEntry(desc),
- mInvoker(func)
- {}
- invoker_function mInvoker;
- virtual void call(const std::string& desc, const LLSD& event) const
- {
- LLSDArgsSource src(desc, event);
- mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src)));
- }
- };
- /**
- * DispatchEntry subclass for dispatching LLSD::Array to functions accepting
- * arbitrary argument types (convertible via LLSDParam)
- */
- struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
- {
- ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func,
- LLSD::Integer arity):
- ParamsDispatchEntry(desc, func),
- mArity(arity)
- {}
- LLSD::Integer mArity;
- virtual LLSD addMetadata(LLSD meta) const
- {
- LLSD array(LLSD::emptyArray());
- // Resize to number of arguments required
- if (mArity)
- array[mArity - 1] = LLSD();
- llassert_always(array.size() == mArity);
- meta["required"] = array;
- return meta;
- }
- };
- /**
- * DispatchEntry subclass for dispatching LLSD::Map to functions accepting
- * arbitrary argument types (convertible via LLSDParam)
- */
- struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry
- {
- MapParamsDispatchEntry(const std::string& name, const std::string& desc,
- const invoker_function& func,
- const LLSD& params, const LLSD& defaults):
- ParamsDispatchEntry(desc, func),
- mMapper(name, params, defaults),
- mRequired(LLSD::emptyMap())
- {
- // Build the set of all param keys, then delete the ones that are
- // optional. What's left are the ones that are required.
- for (LLSD::array_const_iterator pi(params.beginArray()), pend(params.endArray());
- pi != pend; ++pi)
- {
- mRequired[pi->asString()] = LLSD();
- }
- if (defaults.isArray() || defaults.isUndefined())
- {
- // Right-align the params and defaults arrays.
- LLSD::Integer offset = params.size() - defaults.size();
- // Now the name of every defaults[i] is at params[i + offset].
- for (LLSD::Integer i(0), iend(defaults.size()); i < iend; ++i)
- {
- // Erase this optional param from mRequired.
- mRequired.erase(params[i + offset].asString());
- // Instead, make an entry in mOptional with the default
- // param's name and value.
- mOptional[params[i + offset].asString()] = defaults[i];
- }
- }
- else if (defaults.isMap())
- {
- // if defaults is already a map, then it's already in the form we
- // intend to deliver in metadata
- mOptional = defaults;
- // Just delete from mRequired every key appearing in mOptional.
- for (LLSD::map_const_iterator mi(mOptional.beginMap()), mend(mOptional.endMap());
- mi != mend; ++mi)
- {
- mRequired.erase(mi->first);
- }
- }
- }
- LLSDArgsMapper mMapper;
- LLSD mRequired;
- LLSD mOptional;
- virtual void call(const std::string& desc, const LLSD& event) const
- {
- // Just convert from LLSD::Map to LLSD::Array using mMapper, then pass
- // to base-class call() method.
- ParamsDispatchEntry::call(desc, mMapper.map(event));
- }
- virtual LLSD addMetadata(LLSD meta) const
- {
- meta["required"] = mRequired;
- meta["optional"] = mOptional;
- return meta;
- }
- };
- void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name,
- const std::string& desc,
- const invoker_function& invoker,
- LLSD::Integer arity)
- {
- mDispatch.insert(
- DispatchMap::value_type(name, DispatchMap::mapped_type(
- new ArrayParamsDispatchEntry(desc, invoker, arity))));
- }
- void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name,
- const std::string& desc,
- const invoker_function& invoker,
- const LLSD& params,
- const LLSD& defaults)
- {
- mDispatch.insert(
- DispatchMap::value_type(name, DispatchMap::mapped_type(
- new MapParamsDispatchEntry(name, desc, invoker, params, defaults))));
- }
- /// Register a callable by name
- void LLEventDispatcher::add(const std::string& name, const std::string& desc,
- const Callable& callable, const LLSD& required)
- {
- mDispatch.insert(
- DispatchMap::value_type(name, DispatchMap::mapped_type(
- new LLSDDispatchEntry(desc, 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 (! try_call(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 (! try_call(name, event))
- {
- LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey
- << " value '" << name << "'" << LL_ENDL;
- }
- }
- bool LLEventDispatcher::try_call(const LLSD& event) const
- {
- return try_call(event[mKey], event);
- }
- bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const
- {
- DispatchMap::const_iterator found = mDispatch.find(name);
- if (found == mDispatch.end())
- {
- return false;
- }
- // Found the name, so it's plausible to even attempt the call.
- found->second->call(STRINGIZE("LLEventDispatcher(" << mDesc << ") calling '" << name << "'"),
- event);
- return true; // tell caller we were able to call
- }
- LLSD LLEventDispatcher::getMetadata(const std::string& name) const
- {
- DispatchMap::const_iterator found = mDispatch.find(name);
- if (found == mDispatch.end())
- {
- return LLSD();
- }
- LLSD meta;
- meta["name"] = name;
- meta["desc"] = found->second->mDesc;
- return found->second->addMetadata(meta);
- }
- 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;
- }
- LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc):
- mDesc(desc)
- {}