summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2009-12-03 10:38:26 -0500
committerNat Goodspeed <nat@lindenlab.com>2009-12-03 10:38:26 -0500
commit91dd8b2371d40d0e64898b041a71601a301ab8eb (patch)
tree1f1db933ac6531a2c95bbabfa1dca22dfbe6d1df
parent67f11add9c2e05e1c86e30c44c94ee1b7d9205d0 (diff)
parentb626783d14db95ca167fc940230937ab2bcd054f (diff)
Automated merge with ssh://hg.lindenlab.com/viewer/viewer-2-0/
-rw-r--r--indra/llcommon/CMakeLists.txt2
-rw-r--r--indra/llcommon/ll_template_cast.h160
-rw-r--r--indra/llcommon/llevents.cpp25
-rw-r--r--indra/llcommon/llevents.h93
-rw-r--r--indra/llcommon/lllistenerwrapper.h181
-rw-r--r--indra/test/llevents_tut.cpp30
-rw-r--r--indra/viewer_components/login/tests/lllogin_test.cpp2
-rw-r--r--install.xml4
8 files changed, 479 insertions, 18 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index eb0d180a4b..ac7cc2cdac 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -167,6 +167,7 @@ set(llcommon_HEADER_FILES
llinstancetracker.h
llkeythrottle.h
lllazy.h
+ lllistenerwrapper.h
lllinkedqueue.h
llliveappconfig.h
lllivefile.h
@@ -224,6 +225,7 @@ set(llcommon_HEADER_FILES
llversionserver.h
llversionviewer.h
llworkerthread.h
+ ll_template_cast.h
metaclass.h
metaclasst.h
metaproperty.h
diff --git a/indra/llcommon/ll_template_cast.h b/indra/llcommon/ll_template_cast.h
new file mode 100644
index 0000000000..cff58ce00d
--- /dev/null
+++ b/indra/llcommon/ll_template_cast.h
@@ -0,0 +1,160 @@
+/**
+ * @file ll_template_cast.h
+ * @author Nat Goodspeed
+ * @date 2009-11-21
+ * @brief Define ll_template_cast function
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LL_TEMPLATE_CAST_H)
+#define LL_LL_TEMPLATE_CAST_H
+
+/**
+ * Implementation for ll_template_cast() (q.v.).
+ *
+ * Default implementation: trying to cast two completely unrelated types
+ * returns 0. Typically you'd specify T and U as pointer types, but in fact T
+ * can be any type that can be initialized with 0.
+ */
+template <typename T, typename U>
+struct ll_template_cast_impl
+{
+ T operator()(U)
+ {
+ return 0;
+ }
+};
+
+/**
+ * ll_template_cast<T>(some_value) is for use in a template function when
+ * some_value might be of arbitrary type, but you want to recognize type T
+ * specially.
+ *
+ * It's designed for use with pointer types. Example:
+ * @code
+ * struct SpecialClass
+ * {
+ * void someMethod(const std::string&) const;
+ * };
+ *
+ * template <class REALCLASS>
+ * void somefunc(const REALCLASS& instance)
+ * {
+ * const SpecialClass* ptr = ll_template_cast<const SpecialClass*>(&instance);
+ * if (ptr)
+ * {
+ * ptr->someMethod("Call method only available on SpecialClass");
+ * }
+ * }
+ * @endcode
+ *
+ * Why is this better than dynamic_cast<>? Because unless OtherClass is
+ * polymorphic, the following won't even compile (gcc 4.0.1):
+ * @code
+ * OtherClass other;
+ * SpecialClass* ptr = dynamic_cast<SpecialClass*>(&other);
+ * @endcode
+ * to say nothing of this:
+ * @code
+ * void function(int);
+ * SpecialClass* ptr = dynamic_cast<SpecialClass*>(&function);
+ * @endcode
+ * ll_template_cast handles these kinds of cases by returning 0.
+ */
+template <typename T, typename U>
+T ll_template_cast(U value)
+{
+ return ll_template_cast_impl<T, U>()(value);
+}
+
+/**
+ * Implementation for ll_template_cast() (q.v.).
+ *
+ * Implementation for identical types: return same value.
+ */
+template <typename T>
+struct ll_template_cast_impl<T, T>
+{
+ T operator()(T value)
+ {
+ return value;
+ }
+};
+
+/**
+ * LL_TEMPLATE_CONVERTIBLE(dest, source) asserts that, for a value @c s of
+ * type @c source, <tt>ll_template_cast<dest>(s)</tt> will return @c s --
+ * presuming that @c source can be converted to @c dest by the normal rules of
+ * C++.
+ *
+ * By default, <tt>ll_template_cast<dest>(s)</tt> will return 0 unless @c s's
+ * type is literally identical to @c dest. (This is because of the
+ * straightforward application of template specialization rules.) That can
+ * lead to surprising results, e.g.:
+ *
+ * @code
+ * Foo myFoo;
+ * const Foo* fooptr = ll_template_cast<const Foo*>(&myFoo);
+ * @endcode
+ *
+ * Here @c fooptr will be 0 because <tt>&myFoo</tt> is of type <tt>Foo*</tt>
+ * -- @em not <tt>const Foo*</tt>. (Declaring <tt>const Foo myFoo;</tt> would
+ * force the compiler to do the right thing.)
+ *
+ * More disappointingly:
+ * @code
+ * struct Base {};
+ * struct Subclass: public Base {};
+ * Subclass object;
+ * Base* ptr = ll_template_cast<Base*>(&object);
+ * @endcode
+ *
+ * Here @c ptr will be 0 because <tt>&object</tt> is of type
+ * <tt>Subclass*</tt> rather than <tt>Base*</tt>. We @em want this cast to
+ * succeed, but without our help ll_template_cast can't recognize it.
+ *
+ * The following would suffice:
+ * @code
+ * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*);
+ * ...
+ * Base* ptr = ll_template_cast<Base*>(&object);
+ * @endcode
+ *
+ * However, as noted earlier, this is easily fooled:
+ * @code
+ * const Base* ptr = ll_template_cast<const Base*>(&object);
+ * @endcode
+ * would still produce 0 because we haven't yet seen:
+ * @code
+ * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*);
+ * @endcode
+ *
+ * @TODO
+ * This macro should use Boost type_traits facilities for stripping and
+ * re-adding @c const and @c volatile qualifiers so that invoking
+ * LL_TEMPLATE_CONVERTIBLE(dest, source) will automatically generate all
+ * permitted permutations. It's really not fair to the coder to require
+ * separate:
+ * @code
+ * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*);
+ * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*);
+ * LL_TEMPLATE_CONVERTIBLE(const Base*, const Subclass*);
+ * @endcode
+ *
+ * (Naturally we omit <tt>LL_TEMPLATE_CONVERTIBLE(Base*, const Subclass*)</tt>
+ * because that's not permitted by normal C++ assignment anyway.)
+ */
+#define LL_TEMPLATE_CONVERTIBLE(DEST, SOURCE) \
+template <> \
+struct ll_template_cast_impl<DEST, SOURCE> \
+{ \
+ DEST operator()(SOURCE wrapper) \
+ { \
+ return wrapper; \
+ } \
+}
+
+#endif /* ! defined(LL_LL_TEMPLATE_CAST_H) */
diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp
index 4bdfe5a867..31fdd9e60a 100644
--- a/indra/llcommon/llevents.cpp
+++ b/indra/llcommon/llevents.cpp
@@ -459,11 +459,25 @@ void LLEventPump::stopListening(const std::string& name)
bool LLEventStream::post(const LLSD& event)
{
if (! mEnabled)
+ {
return false;
+ }
+ // NOTE NOTE NOTE: Any new access to member data beyond this point should
+ // cause us to move our LLStandardSignal object to a pimpl class along
+ // with said member data. Then the local shared_ptr will preserve both.
+
+ // DEV-43463: capture a local copy of mSignal. We've turned up a
+ // cross-coroutine scenario (described in the Jira) in which this post()
+ // call could end up destroying 'this', the LLEventPump subclass instance
+ // containing mSignal, during the call through *mSignal. So -- capture a
+ // *stack* instance of the shared_ptr, ensuring that our heap
+ // LLStandardSignal object will live at least until post() returns, even
+ // if 'this' gets destroyed during the call.
+ boost::shared_ptr<LLStandardSignal> signal(mSignal);
// Let caller know if any one listener handled the event. This is mostly
// useful when using LLEventStream as a listener for an upstream
// LLEventPump.
- return (*mSignal)(event);
+ return (*signal)(event);
}
/*****************************************************************************
@@ -492,9 +506,16 @@ void LLEventQueue::flush()
// be processed in the *next* flush() call.
EventQueue queue(mEventQueue);
mEventQueue.clear();
+ // NOTE NOTE NOTE: Any new access to member data beyond this point should
+ // cause us to move our LLStandardSignal object to a pimpl class along
+ // with said member data. Then the local shared_ptr will preserve both.
+
+ // DEV-43463: capture a local copy of mSignal. See LLEventStream::post()
+ // for detailed comments.
+ boost::shared_ptr<LLStandardSignal> signal(mSignal);
for ( ; ! queue.empty(); queue.pop_front())
{
- (*mSignal)(queue.front());
+ (*signal)(queue.front());
}
}
diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h
index f52cf33fd8..5646407f6a 100644
--- a/indra/llcommon/llevents.h
+++ b/indra/llcommon/llevents.h
@@ -44,6 +44,7 @@
#include "llsd.h"
#include "llsingleton.h"
#include "lldependencies.h"
+#include "ll_template_cast.h"
/*==========================================================================*|
// override this to allow binding free functions with more parameters
@@ -266,6 +267,14 @@ namespace LLEventDetail
*/
template <typename LISTENER>
LLBoundListener visit_and_connect(const LISTENER& listener,
+ const ConnectFunc& connect_func)
+ {
+ return visit_and_connect("", listener, connect_func);
+ }
+ /// overload of visit_and_connect() when we have a string identifier available
+ template <typename LISTENER>
+ LLBoundListener visit_and_connect(const std::string& name,
+ const LISTENER& listener,
const ConnectFunc& connect_func);
} // namespace LLEventDetail
@@ -468,7 +477,8 @@ public:
// This is why listen() is a template. Conversion from boost::bind()
// to LLEventListener performs type erasure, so it's important to look
// at the boost::bind object itself before that happens.
- return LLEventDetail::visit_and_connect(listener,
+ return LLEventDetail::visit_and_connect(name,
+ listener,
boost::bind(&LLEventPump::listen_impl,
this,
name,
@@ -522,7 +532,7 @@ private:
protected:
/// implement the dispatching
- boost::scoped_ptr<LLStandardSignal> mSignal;
+ boost::shared_ptr<LLStandardSignal> mSignal;
/// valve open?
bool mEnabled;
@@ -664,6 +674,60 @@ private:
LLSD mReqid;
};
+/**
+ * Base class for LLListenerWrapper. See visit_and_connect() and llwrap(). We
+ * provide virtual @c accept_xxx() methods, customization points allowing a
+ * subclass access to certain data visible at LLEventPump::listen() time.
+ * Example subclass usage:
+ *
+ * @code
+ * myEventPump.listen("somename",
+ * llwrap<MyListenerWrapper>(boost::bind(&MyClass::method, instance, _1)));
+ * @endcode
+ *
+ * Because of the anticipated usage (note the anonymous temporary
+ * MyListenerWrapper instance in the example above), the @c accept_xxx()
+ * methods must be @c const.
+ */
+class LL_COMMON_API LLListenerWrapperBase
+{
+public:
+ /// New instance. The accept_xxx() machinery makes it important to use
+ /// shared_ptrs for our data. Many copies of this object are made before
+ /// the instance that actually ends up in the signal, yet accept_xxx()
+ /// will later be called on the @em original instance. All copies of the
+ /// same original instance must share the same data.
+ LLListenerWrapperBase():
+ mName(new std::string),
+ mConnection(new LLBoundListener)
+ {
+ }
+ /// Copy constructor. Copy shared_ptrs to original instance data.
+ LLListenerWrapperBase(const LLListenerWrapperBase& that):
+ mName(that.mName),
+ mConnection(that.mConnection)
+ {
+ }
+
+ /// Ask LLEventPump::listen() for the listener name
+ virtual void accept_name(const std::string& name) const
+ {
+ *mName = name;
+ }
+
+ /// Ask LLEventPump::listen() for the new connection
+ virtual void accept_connection(const LLBoundListener& connection) const
+ {
+ *mConnection = connection;
+ }
+
+protected:
+ /// Listener name.
+ boost::shared_ptr<std::string> mName;
+ /// Connection.
+ boost::shared_ptr<LLBoundListener> mConnection;
+};
+
/*****************************************************************************
* Underpinnings
*****************************************************************************/
@@ -898,7 +962,8 @@ namespace LLEventDetail
* LLStandardSignal, returning LLBoundListener.
*/
template <typename LISTENER>
- LLBoundListener visit_and_connect(const LISTENER& raw_listener,
+ LLBoundListener visit_and_connect(const std::string& name,
+ const LISTENER& raw_listener,
const ConnectFunc& connect_func)
{
// Capture the listener
@@ -913,14 +978,20 @@ namespace LLEventDetail
// which type details have been erased. unwrap() comes from
// Boost.Signals, in case we were passed a boost::ref().
visit_each(visitor, LLEventDetail::unwrap(raw_listener));
- // Make the connection using passed function. At present, wrapping
- // this functionality into this function is a bit silly: we don't
- // really need a visit_and_connect() function any more, just a visit()
- // function. The definition of this function dates from when, after
- // visit_each(), after establishing the connection, we had to
- // postprocess the new connection with the visitor object. That's no
- // longer necessary.
- return connect_func(listener);
+ // Make the connection using passed function.
+ LLBoundListener connection(connect_func(listener));
+ // If the LISTENER is an LLListenerWrapperBase subclass, pass it the
+ // desired information. It's important that we pass the raw_listener
+ // so the compiler can make decisions based on its original type.
+ const LLListenerWrapperBase* lwb =
+ ll_template_cast<const LLListenerWrapperBase*>(&raw_listener);
+ if (lwb)
+ {
+ lwb->accept_name(name);
+ lwb->accept_connection(connection);
+ }
+ // In any case, show new connection to caller.
+ return connection;
}
} // namespace LLEventDetail
diff --git a/indra/llcommon/lllistenerwrapper.h b/indra/llcommon/lllistenerwrapper.h
new file mode 100644
index 0000000000..e7bad1423a
--- /dev/null
+++ b/indra/llcommon/lllistenerwrapper.h
@@ -0,0 +1,181 @@
+/**
+ * @file lllistenerwrapper.h
+ * @author Nat Goodspeed
+ * @date 2009-11-30
+ * @brief Introduce LLListenerWrapper template
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ * Copyright (c) 2009, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLLISTENERWRAPPER_H)
+#define LL_LLLISTENERWRAPPER_H
+
+#include "llevents.h" // LLListenerWrapperBase
+#include <boost/visit_each.hpp>
+
+/**
+ * Template base class for coding wrappers for LLEventPump listeners.
+ *
+ * Derive your listener wrapper from LLListenerWrapper. You must use
+ * LLLISTENER_WRAPPER_SUBCLASS() so your subclass will play nicely with
+ * boost::visit_each (q.v.). That way boost::signals2 can still detect
+ * derivation from LLEventTrackable, and so forth.
+ */
+template <typename LISTENER>
+class LL_COMMON_API LLListenerWrapper: public LLListenerWrapperBase
+{
+public:
+ /// Wrap an arbitrary listener object
+ LLListenerWrapper(const LISTENER& listener):
+ mListener(listener)
+ {}
+
+ /// call
+ virtual bool operator()(const LLSD& event)
+ {
+ return mListener(event);
+ }
+
+ /// Allow boost::visit_each() to peek at our mListener.
+ template <class V>
+ void accept_visitor(V& visitor) const
+ {
+ using boost::visit_each;
+ visit_each(visitor, mListener, 0);
+ }
+
+private:
+ LISTENER mListener;
+};
+
+/**
+ * Specialize boost::visit_each() (leveraging ADL) to peek inside an
+ * LLListenerWrapper<T> to traverse its LISTENER. We borrow the
+ * accept_visitor() pattern from boost::bind(), avoiding the need to make
+ * mListener public.
+ */
+template <class V, typename T>
+void visit_each(V& visitor, const LLListenerWrapper<T>& wrapper, int)
+{
+ wrapper.accept_visitor(visitor);
+}
+
+/// use this (sigh!) for each subclass of LLListenerWrapper<T> you write
+#define LLLISTENER_WRAPPER_SUBCLASS(CLASS) \
+template <class V, typename T> \
+void visit_each(V& visitor, const CLASS<T>& wrapper, int) \
+{ \
+ visit_each(visitor, static_cast<const LLListenerWrapper<T>&>(wrapper), 0); \
+} \
+ \
+/* Have to state this explicitly, rather than using LL_TEMPLATE_CONVERTIBLE, */ \
+/* because the source type is itself a template. */ \
+template <typename T> \
+struct ll_template_cast_impl<const LLListenerWrapperBase*, const CLASS<T>*> \
+{ \
+ const LLListenerWrapperBase* operator()(const CLASS<T>* wrapper) \
+ { \
+ return wrapper; \
+ } \
+}
+
+/**
+ * Make an instance of a listener wrapper. Every wrapper class must be a
+ * template accepting a listener object of arbitrary type. In particular, the
+ * type of a boost::bind() expression is deliberately undocumented. So we
+ * can't just write Wrapper<CorrectType>(boost::bind(...)). Instead we must
+ * write llwrap<Wrapper>(boost::bind(...)).
+ */
+template <template<typename> class WRAPPER, typename T>
+WRAPPER<T> LL_COMMON_API llwrap(const T& listener)
+{
+ return WRAPPER<T>(listener);
+}
+
+/**
+ * This LLListenerWrapper template subclass is used to report entry/exit to an
+ * event listener, by changing this:
+ * @code
+ * someEventPump.listen("MyClass",
+ * boost::bind(&MyClass::method, ptr, _1));
+ * @endcode
+ * to this:
+ * @code
+ * someEventPump.listen("MyClass",
+ * llwrap<LLCoutListener>(
+ * boost::bind(&MyClass::method, ptr, _1)));
+ * @endcode
+ */
+template <class LISTENER>
+class LL_COMMON_API LLCoutListener: public LLListenerWrapper<LISTENER>
+{
+ typedef LLListenerWrapper<LISTENER> super;
+
+public:
+ /// Wrap an arbitrary listener object
+ LLCoutListener(const LISTENER& listener):
+ super(listener)
+ {}
+
+ /// call
+ virtual bool operator()(const LLSD& event)
+ {
+ std::cout << "Entering listener " << *super::mName << " with " << event << std::endl;
+ bool handled = super::operator()(event);
+ std::cout << "Leaving listener " << *super::mName;
+ if (handled)
+ {
+ std::cout << " (handled)";
+ }
+ std::cout << std::endl;
+ return handled;
+ }
+};
+
+LLLISTENER_WRAPPER_SUBCLASS(LLCoutListener);
+
+/**
+ * This LLListenerWrapper template subclass is used to log entry/exit to an
+ * event listener, by changing this:
+ * @code
+ * someEventPump.listen("MyClass",
+ * boost::bind(&MyClass::method, ptr, _1));
+ * @endcode
+ * to this:
+ * @code
+ * someEventPump.listen("MyClass",
+ * llwrap<LLLogListener>(
+ * boost::bind(&MyClass::method, ptr, _1)));
+ * @endcode
+ */
+template <class LISTENER>
+class LL_COMMON_API LLLogListener: public LLListenerWrapper<LISTENER>
+{
+ typedef LLListenerWrapper<LISTENER> super;
+
+public:
+ /// Wrap an arbitrary listener object
+ LLLogListener(const LISTENER& listener):
+ super(listener)
+ {}
+
+ /// call
+ virtual bool operator()(const LLSD& event)
+ {
+ LL_DEBUGS("LLLogListener") << "Entering listener " << *super::mName << " with " << event << LL_ENDL;
+ bool handled = super::operator()(event);
+ LL_DEBUGS("LLLogListener") << "Leaving listener " << *super::mName;
+ if (handled)
+ {
+ LL_CONT << " (handled)";
+ }
+ LL_CONT << LL_ENDL;
+ return handled;
+ }
+};
+
+LLLISTENER_WRAPPER_SUBCLASS(LLLogListener);
+
+#endif /* ! defined(LL_LLLISTENERWRAPPER_H) */
diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp
index 31130c3c79..e58b10ce07 100644
--- a/indra/test/llevents_tut.cpp
+++ b/indra/test/llevents_tut.cpp
@@ -21,6 +21,7 @@
#define testable public
#include "llevents.h"
#undef testable
+#include "lllistenerwrapper.h"
// STL headers
// std headers
#include <iostream>
@@ -639,6 +640,33 @@ namespace tut
heaptest.post(2);
}
+ template<> template<>
+ void events_object::test<15>()
+ {
+ // This test ensures that using an LLListenerWrapper subclass doesn't
+ // block Boost.Signals2 from recognizing a bound LLEventTrackable
+ // subclass.
+ set_test_name("listen(llwrap<LLLogListener>(boost::bind(...TempTrackableListener ref...)))");
+ bool live = false;
+ LLEventPump& heaptest(pumps.obtain("heaptest"));
+ LLBoundListener connection;
+ {
+ TempTrackableListener tempListener("temp", live);
+ ensure("TempTrackableListener constructed", live);
+ connection = heaptest.listen(tempListener.getName(),
+ llwrap<LLLogListener>(
+ boost::bind(&TempTrackableListener::call,
+ boost::ref(tempListener), _1)));
+ heaptest.post(1);
+ check_listener("received", tempListener, 1);
+ } // presumably this will make tempListener go away?
+ // verify that
+ ensure("TempTrackableListener destroyed", ! live);
+ ensure("implicit disconnect", ! connection.connected());
+ // now just make sure we don't blow up trying to access a freed object!
+ heaptest.post(2);
+ }
+
class TempSharedListener: public TempListener,
public boost::enable_shared_from_this<TempSharedListener>
{
@@ -649,7 +677,7 @@ namespace tut
};
template<> template<>
- void events_object::test<15>()
+ void events_object::test<16>()
{
set_test_name("listen(boost::bind(...TempSharedListener ref...))");
#if 0
diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp
index 99ea796ad0..56c21016bd 100644
--- a/indra/viewer_components/login/tests/lllogin_test.cpp
+++ b/indra/viewer_components/login/tests/lllogin_test.cpp
@@ -416,7 +416,6 @@ namespace tut
ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline");
}
-/*
template<> template<>
void llviewerlogin_object::test<5>()
{
@@ -452,5 +451,4 @@ namespace tut
ensure_equals("SRV Failure", listener.lastEvent()["change"].asString(), "fail.login");
}
-*/
}
diff --git a/install.xml b/install.xml
index 3e49087726..af94de9f8e 100644
--- a/install.xml
+++ b/install.xml
@@ -193,9 +193,9 @@
<key>darwin</key>
<map>
<key>md5sum</key>
- <string>74f3a765644927c93fa3bc7acc730552</string>
+ <string>609c469ee1857723260b5a9943b9c2c1</string>
<key>url</key>
- <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.34.1-darwin-20090805.tar.bz2</uri>
+ <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.39.0-darwin-20091202.tar.bz2</uri>
</map>
<key>linux</key>
<map>