diff --git a/Modules/Core/Common/include/itkMetaDataDictionary.h b/Modules/Core/Common/include/itkMetaDataDictionary.h index 99f4e3bcc39..a49d79192c1 100644 --- a/Modules/Core/Common/include/itkMetaDataDictionary.h +++ b/Modules/Core/Common/include/itkMetaDataDictionary.h @@ -19,6 +19,7 @@ #define itkMetaDataDictionary_h #include "itkMetaDataObjectBase.h" +#include // For std::equal #include #include #include @@ -81,6 +82,35 @@ class ITKCommon_EXPORT MetaDataDictionary // Destructor virtual ~MetaDataDictionary(); + /** Returns (metaDataDictionary1 == metaDataDictionary2). */ + friend bool + operator==(const Self & lhs, const Self & rhs) + { + using KeyValuePair = MetaDataDictionaryMapType::value_type; + + return (lhs.m_Dictionary == rhs.m_Dictionary) || + ((lhs.m_Dictionary != nullptr) && (rhs.m_Dictionary != nullptr) && + (lhs.m_Dictionary->size() == rhs.m_Dictionary->size()) && + std::equal(lhs.m_Dictionary->cbegin(), + lhs.m_Dictionary->cend(), + rhs.m_Dictionary->cbegin(), + [](const KeyValuePair & keyValuePair1, const KeyValuePair & keyValuePair2) { + const auto & value1 = keyValuePair1.second; + const auto & value2 = keyValuePair2.second; + return (keyValuePair1.first == keyValuePair2.first) && + ((value1 == value2) || + ((value1 != nullptr) && (value2 != nullptr) && (*value1 == *value2))); + })); + } + + /** Returns (metaDataDictionary1 != metaDataDictionary2). */ + friend bool + operator!=(const Self & lhs, const Self & rhs) + { + return !(lhs == rhs); + } + + /** Returns a vector of keys to the key/value entries in the * dictionary. Iterate through the dictionary using these keys. */ diff --git a/Modules/Core/Common/include/itkMetaDataObject.h b/Modules/Core/Common/include/itkMetaDataObject.h index b4fef18055d..dd20f1f1911 100644 --- a/Modules/Core/Common/include/itkMetaDataObject.h +++ b/Modules/Core/Common/include/itkMetaDataObject.h @@ -130,11 +130,33 @@ class ITK_TEMPLATE_EXPORT MetaDataObject : public MetaDataObjectBase void Print(std::ostream & os) const override; + /** Returns (metaDataObject1 == metaDataObject2). */ + friend bool + operator==(const Self & lhs, const Self & rhs) + { + return lhs.m_MetaDataObjectValue == rhs.m_MetaDataObjectValue; + } + + /** Returns (metaDataObject1 != metaDataObject2). */ + friend bool + operator!=(const Self & lhs, const Self & rhs) + { + return !(lhs == rhs); + } + protected: MetaDataObject() = default; ~MetaDataObject() override = default; private: + /** Internal helper function used to implement operator== for MetaDataObjectBase. */ + bool + Equal(const MetaDataObjectBase & metaDataObjectBase) const override + { + const auto metaDataObject = dynamic_cast(&metaDataObjectBase); + return (metaDataObject != nullptr) && (*this == *metaDataObject); + } + /** * A variable to store this derived type. * \author Hans J. Johnson diff --git a/Modules/Core/Common/include/itkMetaDataObjectBase.h b/Modules/Core/Common/include/itkMetaDataObjectBase.h index 6b5199f5cb9..b5484665d89 100644 --- a/Modules/Core/Common/include/itkMetaDataObjectBase.h +++ b/Modules/Core/Common/include/itkMetaDataObjectBase.h @@ -72,6 +72,21 @@ class ITKCommon_EXPORT MetaDataObjectBase : public LightObject virtual const std::type_info & GetMetaDataObjectTypeInfo() const; + /** Returns (metaDataBase1 == metaDataBase2). */ + friend bool + operator==(const Self & lhs, const Self & rhs) + { + return lhs.Equal(rhs); + } + + /** Returns (metaDataBase1 != metaDataBase2). */ + friend bool + operator!=(const Self & lhs, const Self & rhs) + { + return !(lhs == rhs); + } + + /** * Defines the default behavior for printing out this element * \param os An output stream @@ -82,6 +97,10 @@ class ITKCommon_EXPORT MetaDataObjectBase : public LightObject protected: MetaDataObjectBase(); ~MetaDataObjectBase() override; + +private: + virtual bool + Equal(const MetaDataObjectBase &) const = 0; }; } // namespace itk diff --git a/Modules/Core/Common/test/itkMetaDataDictionaryGTest.cxx b/Modules/Core/Common/test/itkMetaDataDictionaryGTest.cxx index 8adaeb0f455..558bb39f30b 100644 --- a/Modules/Core/Common/test/itkMetaDataDictionaryGTest.cxx +++ b/Modules/Core/Common/test/itkMetaDataDictionaryGTest.cxx @@ -252,3 +252,53 @@ TEST(MetaDataDictionary, CopyOnWrite) EXPECT_EQ(f, 1.0f); } } + + +TEST(MetaDataDictionary, Equal) +{ + const auto expectEqual = [](const itk::MetaDataDictionary & object1, const itk::MetaDataDictionary & object2) { + // Test that equal objects can be used as arguments to GoogleTest EXPECT_EQ. + EXPECT_EQ(object1, object2); + EXPECT_EQ(object2, object1); + + // Test symmetry, as well as consistency between equal and unequal. + EXPECT_TRUE(object1 == object2); + EXPECT_TRUE(object2 == object1); + EXPECT_FALSE(object1 != object2); + EXPECT_FALSE(object2 != object1); + }; + + const auto expectUnequal = [](const itk::MetaDataDictionary & object1, const itk::MetaDataDictionary & object2) { + // Test that unequal objects can be used as arguments to GoogleTest EXPECT_NE. + EXPECT_NE(object1, object2); + EXPECT_NE(object2, object1); + + // Test symmetry, as well as consistency between equal and unequal. + EXPECT_TRUE(object1 != object2); + EXPECT_TRUE(object2 != object1); + EXPECT_FALSE(object1 == object2); + EXPECT_FALSE(object2 == object1); + }; + + const itk::MetaDataDictionary defaultMetaDataDictionary{}; + + expectEqual(defaultMetaDataDictionary, {}); + + const auto createMetaDataDictionary = [](const int value) { + itk::MetaDataDictionary metaDataDictionary; + itk::EncapsulateMetaData(metaDataDictionary, "key", value); + return metaDataDictionary; + }; + + const auto metaDataDictionary1 = createMetaDataDictionary(1); + const auto metaDataDictionary2 = createMetaDataDictionary(2); + + expectEqual(metaDataDictionary1, metaDataDictionary1); + expectEqual(metaDataDictionary2, metaDataDictionary2); + expectEqual(metaDataDictionary1, createMetaDataDictionary(1)); + expectEqual(metaDataDictionary2, createMetaDataDictionary(2)); + + expectUnequal(metaDataDictionary1, metaDataDictionary2); + expectUnequal(metaDataDictionary1, defaultMetaDataDictionary); + expectUnequal(metaDataDictionary2, defaultMetaDataDictionary); +}