From a81c084debb4075f36bacd59cbe332c2f0548d50 Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Tue, 26 May 2009 22:36:38 +0000
Subject: [PATCH] Add llsd_equals(), a function whose absence sorely puzzles me

---
 indra/llcommon/llsdutil.cpp | 92 +++++++++++++++++++++++++++++++++++++
 indra/llcommon/llsdutil.h   |  3 ++
 indra/test/llsdutil_tut.cpp | 58 ++++++++++++++++++++++-
 3 files changed, 151 insertions(+), 2 deletions(-)

diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp
index 643720cebe2..c8d8030e875 100644
--- a/indra/llcommon/llsdutil.cpp
+++ b/indra/llcommon/llsdutil.cpp
@@ -576,3 +576,95 @@ std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::str
     // bad LLSD doesn't define isConvertible(Type to, Type from).
     return match_types(prototype.type(), TypeVector(), data.type(), pfx);
 }
+
+bool llsd_equals(const LLSD& lhs, const LLSD& rhs)
+{
+    // We're comparing strict equality of LLSD representation rather than
+    // performing any conversions. So if the types aren't equal, the LLSD
+    // values aren't equal.
+    if (lhs.type() != rhs.type())
+    {
+        return false;
+    }
+
+    // Here we know both types are equal. Now compare values.
+    switch (lhs.type())
+    {
+    case LLSD::TypeUndefined:
+        // Both are TypeUndefined. There's nothing more to know.
+        return true;
+
+#define COMPARE_SCALAR(type)                                    \
+    case LLSD::Type##type:                                      \
+        /* LLSD::URI has operator!=() but not operator==() */   \
+        /* rely on the optimizer for all others */              \
+        return (! (lhs.as##type() != rhs.as##type()))
+
+    COMPARE_SCALAR(Boolean);
+    COMPARE_SCALAR(Integer);
+    // The usual caveats about comparing floating-point numbers apply. This is
+    // only useful when we expect identical bit representation for a given
+    // Real value, e.g. for integer-valued Reals.
+    COMPARE_SCALAR(Real);
+    COMPARE_SCALAR(String);
+    COMPARE_SCALAR(UUID);
+    COMPARE_SCALAR(Date);
+    COMPARE_SCALAR(URI);
+    COMPARE_SCALAR(Binary);
+
+#undef COMPARE_SCALAR
+
+    case LLSD::TypeArray:
+    {
+        LLSD::array_const_iterator
+            lai(lhs.beginArray()), laend(lhs.endArray()),
+            rai(rhs.beginArray()), raend(rhs.endArray());
+        // Compare array elements, walking the two arrays in parallel.
+        for ( ; lai != laend && rai != raend; ++lai, ++rai)
+        {
+            // If any one array element is unequal, the arrays are unequal.
+            if (! llsd_equals(*lai, *rai))
+                return false;
+        }
+        // Here we've reached the end of one or the other array. They're equal
+        // only if they're BOTH at end: that is, if they have equal length too.
+        return (lai == laend && rai == raend);
+    }
+
+    case LLSD::TypeMap:
+    {
+        // Build a set of all rhs keys.
+        std::set<LLSD::String> rhskeys;
+        for (LLSD::map_const_iterator rmi(rhs.beginMap()), rmend(rhs.endMap());
+             rmi != rmend; ++rmi)
+        {
+            rhskeys.insert(rmi->first);
+        }
+        // Now walk all the lhs keys.
+        for (LLSD::map_const_iterator lmi(lhs.beginMap()), lmend(lhs.endMap());
+             lmi != lmend; ++lmi)
+        {
+            // Try to erase this lhs key from the set of rhs keys. If rhs has
+            // no such key, the maps are unequal. erase(key) returns count of
+            // items erased.
+            if (rhskeys.erase(lmi->first) != 1)
+                return false;
+            // Both maps have the current key. Compare values.
+            if (! llsd_equals(lmi->second, rhs[lmi->first]))
+                return false;
+        }
+        // We've now established that all the lhs keys have equal values in
+        // both maps. The maps are equal unless rhs contains a superset of
+        // those keys.
+        return rhskeys.empty();
+    }
+
+    default:
+        // We expect that every possible type() value is specifically handled
+        // above. Failing to extend this switch to support a new LLSD type is
+        // an error that must be brought to the coder's attention.
+        LL_ERRS("llsd_equals") << "llsd_equals(" << lhs << ", " << rhs << "): "
+            "unknown type " << lhs.type() << LL_ENDL;
+        return false;               // pacify the compiler
+    }
+}
diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h
index a4175be4508..31d219da520 100644
--- a/indra/llcommon/llsdutil.h
+++ b/indra/llcommon/llsdutil.h
@@ -129,6 +129,9 @@ LL_COMMON_API BOOL compare_llsd_with_template(
  */
 LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx="");
 
+/// Deep equality
+bool llsd_equals(const LLSD& lhs, const LLSD& rhs);
+
 // Simple function to copy data out of input & output iterators if
 // there is no need for casting.
 template<typename Input> LLSD llsd_copy_array(Input iter, Input end)
diff --git a/indra/test/llsdutil_tut.cpp b/indra/test/llsdutil_tut.cpp
index 35ab80e7916..d125bb00050 100644
--- a/indra/test/llsdutil_tut.cpp
+++ b/indra/test/llsdutil_tut.cpp
@@ -45,6 +45,7 @@
 #include "llquaternion.h"
 #include "llsdutil.h"
 #include "llsdutil_math.h"
+#include "stringize.h"
 #include <set>
 #include <boost/range.hpp>
 
@@ -207,14 +208,16 @@ namespace tut
         map.insert("URI",       LLSD::URI());
         map.insert("Binary",    LLSD::Binary());
         map.insert("Map",       LLSD().insert("foo", LLSD()));
-        // array can't be constructed on the fly
+        // Only an empty array can be constructed on the fly
         LLSD array;
         array.append(LLSD());
         map.insert("Array",     array);
 
         // These iterators are declared outside our various for loops to avoid
         // fatal MSVC warning: "I used to be broken, but I'm all better now!"
-        LLSD::map_const_iterator mi(map.beginMap()), mend(map.endMap());
+        LLSD::map_const_iterator mi, mend(map.endMap());
+
+        /*-------------------------- llsd_matches --------------------------*/
 
         // empty prototype matches anything
         for (mi = map.beginMap(); mi != mend; ++mi)
@@ -337,5 +340,56 @@ namespace tut
             static const char* matches[] = { "Binary" };
             test_matches("Binary", map, boost::begin(matches), boost::end(matches));
         }
+
+        /*-------------------------- llsd_equals ---------------------------*/
+
+        // Cross-product of each LLSD type with every other
+        for (LLSD::map_const_iterator lmi(map.beginMap()), lmend(map.endMap());
+             lmi != lmend; ++lmi)
+        {
+            for (LLSD::map_const_iterator rmi(map.beginMap()), rmend(map.endMap());
+                 rmi != rmend; ++rmi)
+            {
+                // Name this test based on the map keys naming the types of
+                // interest, e.g "String::Integer".
+                // We expect the values (xmi->second) to be equal if and only
+                // if the type names (xmi->first) are equal.
+                ensure(STRINGIZE(lmi->first << "::" << rmi->first),
+                       bool(lmi->first == rmi->first) ==
+                       bool(llsd_equals(lmi->second, rmi->second)));
+            }
+        }
+
+        // Array cases
+        LLSD rarray;
+        rarray.append(1.0);
+        rarray.append(2);
+        rarray.append("3");
+        LLSD larray(rarray);
+        ensure("llsd_equals(equal arrays)", llsd_equals(larray, rarray));
+        rarray[2] = "4";
+        ensure("llsd_equals(different [2])", ! llsd_equals(larray, rarray));
+        rarray = larray;
+        rarray.append(LLSD::Date());
+        ensure("llsd_equals(longer right array)", ! llsd_equals(larray, rarray));
+        rarray = larray;
+        rarray.erase(2);
+        ensure("llsd_equals(shorter right array)", ! llsd_equals(larray, rarray));
+
+        // Map cases
+        LLSD rmap;
+        rmap["San Francisco"] = 65;
+        rmap["Phoenix"] = 92;
+        rmap["Boston"] = 77;
+        LLSD lmap(rmap);
+        ensure("llsd_equals(equal maps)", llsd_equals(lmap, rmap));
+        rmap["Boston"] = 80;
+        ensure("llsd_equals(different [\"Boston\"])", ! llsd_equals(lmap, rmap));
+        rmap = lmap;
+        rmap["Atlanta"] = 95;
+        ensure("llsd_equals(superset right map)", ! llsd_equals(lmap, rmap));
+        rmap = lmap;
+        lmap["Seattle"] = 72;
+        ensure("llsd_equals(superset left map)", ! llsd_equals(lmap, rmap));
     }
 }
-- 
GitLab