Merge pull request #241 from oatpp/dto_hashcode_equals

Dto hashcode equals
This commit is contained in:
Leonid Stryzhevskyi 2020-05-14 01:42:14 +03:00 committed by GitHub
commit b209cc2684
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 438 additions and 24 deletions

View File

@ -11,6 +11,7 @@ Contents:
- [Object-Mapping std Collections](#object-mapping-std-collections)
- [Type oatpp::Any](#type-oatppany)
- [Type oatpp::Enum](#object-mapping-enum)
- [DTO - Hashcode & Equals](#dto-hashcode-and-equals)
## No more explicit ObjectWrapper
@ -272,3 +273,27 @@ class MyDto : public oatpp::Object {
}
}
```
## DTO Hashcode and Equals
Now DTOs can be used as a Key in `unordered_map` and `unordered_set`.
The convenience `DTO_HC_EQ` (DTO_HASHCODE_AND_EQUALS) macro has been added.
```cpp
class User : public oatpp::Object {
DTO_INIT(User, Object)
DTO_FIELD(String, firstName);
DTO_FIELD(String, lastName);
DTO_HC_EQ(firstName, lastName) // List key fields that count in std::hash and "==","!=" operators.
};
```
The `DTO_HC_EQ` macro works taking into account the `DTO_HC_EQ` declared in the parent DTO class.
If no `DTO_HC_EQ` is declared in none of the DTO's parent classes the default behavior is:
- `std::hash` - is `v_uint64` representation of object address.
- operators `==` and `!=` - is comparison of object addresses.

View File

@ -34,7 +34,7 @@
public: \
typedef TYPE_NAME Z__CLASS; \
typedef TYPE_EXTEND Z__CLASS_EXTENDED; \
typedef oatpp::data::mapping::type::ObjectWrapper<Z__CLASS, oatpp::data::mapping::type::__class::Object<Z__CLASS>> ObjectWrapper; \
typedef oatpp::data::mapping::type::DTOWrapper<Z__CLASS> ObjectWrapper; \
typedef ObjectWrapper __Wrapper; \
public: \
OBJECT_POOL(DTO_OBJECT_POOL_##TYPE_NAME, TYPE_NAME, 32) \
@ -121,3 +121,43 @@ TYPE::__Wrapper NAME
*/
#define DTO_FIELD(TYPE, ...) \
OATPP_MACRO_EXPAND(OATPP_MACRO_MACRO_SELECTOR(OATPP_MACRO_DTO_FIELD_, (__VA_ARGS__)) (TYPE, __VA_ARGS__))
// FOR EACH
#define OATPP_MACRO_DTO_HC_EQ_PARAM_HC(INDEX, COUNT, X) \
result = ((result << 5) - result) + std::hash<decltype(X)>{}(X);
#define OATPP_MACRO_DTO_HC_EQ_PARAM_EQ(INDEX, COUNT, X) \
&& X == other.X
#define DTO_HASHCODE_AND_EQUALS(...) \
v_uint64 defaultHashCode() const override { \
return 1; \
} \
\
bool defaultEquals(const Object& other) const override { \
return true; \
} \
\
v_uint64 hashCode() const { \
v_uint64 result = 1; \
result = ((result << 5) - result) + static_cast<const Z__CLASS_EXTENDED&>(*this).hashCode(); \
OATPP_MACRO_FOREACH(OATPP_MACRO_DTO_HC_EQ_PARAM_HC, __VA_ARGS__) \
return result; \
} \
\
bool operator==(const Z__CLASS& other) const { \
return static_cast<const Z__CLASS_EXTENDED&>(*this) == static_cast<const Z__CLASS_EXTENDED&>(other) \
OATPP_MACRO_FOREACH(OATPP_MACRO_DTO_HC_EQ_PARAM_EQ, __VA_ARGS__) \
; \
} \
\
bool operator!=(const Z__CLASS& other) const { \
return !this->operator==(other); \
}
/**
* Hashcode and Equals macro. <br>
* List DTO-fields which should count in hashcode and equals operators.
*/
#define DTO_HC_EQ(...) DTO_HASHCODE_AND_EQUALS(__VA_ARGS__)

View File

@ -31,3 +31,10 @@
#undef OATPP_MACRO_DTO_FIELD_1
#undef OATPP_MACRO_DTO_FIELD_2
#undef DTO_FIELD
// Hashcode & Equals
#undef OATPP_MACRO_DTO_HC_EQ_PARAM_HC
#undef OATPP_MACRO_DTO_HC_EQ_PARAM_EQ
#undef DTO_HASHCODE_AND_EQUALS
#undef DTO_HC_EQ

View File

@ -39,6 +39,8 @@
#include "oatpp/core/base/memory/ObjectPool.hpp"
#include "oatpp/core/base/Countable.hpp"
#include <type_traits>
namespace oatpp { namespace data { namespace mapping { namespace type {
namespace __class {
@ -121,9 +123,89 @@ public:
static oatpp::data::mapping::type::Type::Properties map;
return &map;
}
virtual v_uint64 defaultHashCode() const {
return (v_uint64) reinterpret_cast<v_buff_usize>(this);
}
virtual bool defaultEquals(const Object& other) const {
return this == &other;
}
v_uint64 hashCode() const {
return defaultHashCode();
}
bool operator==(const Object& other) const {
return defaultEquals(other);
}
};
template<class ObjT>
class DTOWrapper : public ObjectWrapper<ObjT, __class::Object<ObjT>> {
public:
typedef ObjT TemplateObjectType;
typedef __class::Object<ObjT> TemplateObjectClass;
public:
OATPP_DEFINE_OBJECT_WRAPPER_DEFAULTS(DTOWrapper, TemplateObjectType, TemplateObjectClass)
static DTOWrapper createShared() {
return std::make_shared<TemplateObjectType>();
}
template<typename T,
typename enabled = typename std::enable_if<std::is_same<T, std::nullptr_t>::value, void>::type
>
inline bool operator == (T){
return this->m_ptr.get() == nullptr;
}
template<typename T,
typename enabled = typename std::enable_if<std::is_same<T, std::nullptr_t>::value, void>::type
>
inline bool operator != (T){
return this->m_ptr.get() != nullptr;
}
template<typename T,
typename enabled = typename std::enable_if<std::is_same<T, DTOWrapper>::value, void>::type
>
inline bool operator == (const T &other) const {
if(this->m_ptr.get() == other.m_ptr.get()) return true;
if(!this->m_ptr || !other.m_ptr) return false;
return *this->m_ptr == *other.m_ptr;
}
template<typename T,
typename enabled = typename std::enable_if<std::is_same<T, DTOWrapper>::value, void>::type
>
inline bool operator != (const T &other) const {
return !operator == (other);
}
};
}}}}
namespace std {
template<class T>
struct hash<oatpp::data::mapping::type::DTOWrapper<T>> {
typedef oatpp::data::mapping::type::DTOWrapper<T> argument_type;
typedef v_uint64 result_type;
result_type operator()(argument_type const &ow) const noexcept {
if(ow) {
return ow->hashCode();
}
return 0;
}
};
}
#endif /* oatpp_data_type_Object_hpp */

View File

@ -549,9 +549,10 @@ namespace std {
struct hash<oatpp::data::mapping::type::String> {
typedef oatpp::data::mapping::type::String argument_type;
typedef v_uint32 result_type;
typedef v_uint64 result_type;
result_type operator()(argument_type const& s) const noexcept {
if(s.get() == nullptr) return 0;
p_char8 data = s->getData();
result_type result = 0;
@ -570,7 +571,7 @@ namespace std {
struct hash<oatpp::data::mapping::type::Boolean> {
typedef oatpp::data::mapping::type::Boolean argument_type;
typedef v_uint8 result_type;
typedef v_uint64 result_type;
result_type operator()(argument_type const& v) const noexcept {
if(v.get() == nullptr) return 2;
@ -583,7 +584,7 @@ namespace std {
struct hash<oatpp::data::mapping::type::Int8> {
typedef oatpp::data::mapping::type::Int8 argument_type;
typedef v_uint8 result_type;
typedef v_uint64 result_type;
result_type operator()(argument_type const& v) const noexcept {
if(v.get() == nullptr) return 0;
@ -596,7 +597,7 @@ namespace std {
struct hash<oatpp::data::mapping::type::UInt8> {
typedef oatpp::data::mapping::type::UInt8 argument_type;
typedef v_uint8 result_type;
typedef v_uint64 result_type;
result_type operator()(argument_type const& v) const noexcept {
if(v.get() == nullptr) return 0;
@ -609,7 +610,7 @@ namespace std {
struct hash<oatpp::data::mapping::type::Int16> {
typedef oatpp::data::mapping::type::Int16 argument_type;
typedef v_uint16 result_type;
typedef v_uint64 result_type;
result_type operator()(argument_type const& v) const noexcept {
if(v.get() == nullptr) return 0;
@ -622,7 +623,7 @@ namespace std {
struct hash<oatpp::data::mapping::type::UInt16> {
typedef oatpp::data::mapping::type::UInt16 argument_type;
typedef v_uint16 result_type;
typedef v_uint64 result_type;
result_type operator()(argument_type const& v) const noexcept {
if(v.get() == nullptr) return 0;
@ -635,7 +636,7 @@ namespace std {
struct hash<oatpp::data::mapping::type::Int32> {
typedef oatpp::data::mapping::type::Int32 argument_type;
typedef v_uint32 result_type;
typedef v_uint64 result_type;
result_type operator()(argument_type const& v) const noexcept {
if(v.get() == nullptr) return 0;
@ -648,7 +649,7 @@ namespace std {
struct hash<oatpp::data::mapping::type::UInt32> {
typedef oatpp::data::mapping::type::UInt32 argument_type;
typedef v_uint32 result_type;
typedef v_uint64 result_type;
result_type operator()(argument_type const& v) const noexcept {
if(v.get() == nullptr) return 0;
@ -687,11 +688,11 @@ namespace std {
struct hash<oatpp::data::mapping::type::Float32> {
typedef oatpp::data::mapping::type::Float32 argument_type;
typedef v_uint32 result_type;
typedef v_uint64 result_type;
result_type operator()(argument_type const& v) const noexcept {
if(v.get() == nullptr) return 0;
return *((result_type*) v.get());
return *((v_uint32*) v.get());
}
};

View File

@ -423,7 +423,7 @@ template<>
struct hash<oatpp::data::mapping::type::Void> {
typedef oatpp::data::mapping::type::Void argument_type;
typedef v_buff_usize result_type;
typedef v_uint64 result_type;
result_type operator()(argument_type const& v) const noexcept {
return (result_type) v.get();

View File

@ -334,7 +334,7 @@ namespace std {
struct hash<oatpp::data::share::StringKeyLabel> {
typedef oatpp::data::share::StringKeyLabel argument_type;
typedef v_uint32 result_type;
typedef v_uint64 result_type;
result_type operator()(oatpp::data::share::StringKeyLabel const& s) const noexcept {
@ -354,7 +354,7 @@ namespace std {
struct hash<oatpp::data::share::StringKeyLabelCI> {
typedef oatpp::data::share::StringKeyLabelCI argument_type;
typedef v_uint32 result_type;
typedef v_uint64 result_type;
result_type operator()(oatpp::data::share::StringKeyLabelCI const& s) const noexcept {
@ -374,7 +374,7 @@ namespace std {
struct hash<oatpp::data::share::StringKeyLabelCI_FAST> {
typedef oatpp::data::share::StringKeyLabelCI_FAST argument_type;
typedef v_uint32 result_type;
typedef v_uint64 result_type;
result_type operator()(oatpp::data::share::StringKeyLabelCI_FAST const& s) const noexcept {

View File

@ -735,7 +735,7 @@ namespace std {
struct hash<oatpp::web::protocol::http::Status> {
typedef oatpp::web::protocol::http::Status argument_type;
typedef v_uint32 result_type;
typedef v_uint64 result_type;
result_type operator()(oatpp::web::protocol::http::Status const& s) const noexcept {
return s.code;

View File

@ -19,6 +19,8 @@ add_executable(oatppAllTests
oatpp/core/data/mapping/type/EnumTest.hpp
oatpp/core/data/mapping/type/ListTest.cpp
oatpp/core/data/mapping/type/ListTest.hpp
oatpp/core/data/mapping/type/ObjectTest.cpp
oatpp/core/data/mapping/type/ObjectTest.hpp
oatpp/core/data/mapping/type/ObjectWrapperTest.cpp
oatpp/core/data/mapping/type/ObjectWrapperTest.hpp
oatpp/core/data/mapping/type/PairListTest.cpp

View File

@ -45,6 +45,7 @@
#include "oatpp/core/data/mapping/type/VectorTest.hpp"
#include "oatpp/core/data/mapping/type/UnorderedSetTest.hpp"
#include "oatpp/core/data/mapping/type/ListTest.hpp"
#include "oatpp/core/data/mapping/type/ObjectTest.hpp"
#include "oatpp/core/data/mapping/type/StringTest.hpp"
#include "oatpp/core/data/mapping/type/PrimitiveTest.hpp"
#include "oatpp/core/data/mapping/type/ObjectWrapperTest.hpp"
@ -75,7 +76,7 @@ void runTests() {
OATPP_LOGD("aaa", "coroutine size=%d", sizeof(oatpp::async::AbstractCoroutine));
OATPP_LOGD("aaa", "action size=%d", sizeof(oatpp::async::Action));
/*
OATPP_RUN_TEST(oatpp::test::base::CommandLineArgumentsTest);
OATPP_RUN_TEST(oatpp::test::memory::MemoryPoolTest);
@ -98,23 +99,22 @@ void runTests() {
OATPP_RUN_TEST(oatpp::test::core::data::mapping::type::PrimitiveTest);
OATPP_RUN_TEST(oatpp::test::core::data::mapping::type::ListTest);
OATPP_RUN_TEST(oatpp::test::core::data::mapping::type::VectorTest);
*/
OATPP_RUN_TEST(oatpp::test::core::data::mapping::type::UnorderedSetTest);
/*
OATPP_RUN_TEST(oatpp::test::core::data::mapping::type::PairListTest);
OATPP_RUN_TEST(oatpp::test::core::data::mapping::type::UnorderedMapTest);
OATPP_RUN_TEST(oatpp::test::core::data::mapping::type::AnyTest);
OATPP_RUN_TEST(oatpp::test::core::data::mapping::type::EnumTest);
OATPP_RUN_TEST(oatpp::test::core::data::mapping::type::ObjectTest);
OATPP_RUN_TEST(oatpp::test::async::LockTest);
OATPP_RUN_TEST(oatpp::test::parser::CaretTest);
OATPP_RUN_TEST(oatpp::test::parser::json::mapping::EnumTest);
*/
OATPP_RUN_TEST(oatpp::test::parser::json::mapping::UnorderedSetTest);
/*
OATPP_RUN_TEST(oatpp::test::parser::json::mapping::DeserializerTest);
OATPP_RUN_TEST(oatpp::test::parser::json::mapping::DTOMapperPerfTest);
OATPP_RUN_TEST(oatpp::test::parser::json::mapping::DTOMapperTest);
@ -196,7 +196,7 @@ void runTests() {
test_port.run();
}
*/
}
}

View File

@ -0,0 +1,215 @@
/***************************************************************************
*
* Project _____ __ ____ _ _
* ( _ ) /__\ (_ _)_| |_ _| |_
* )(_)( /(__)\ )( (_ _)(_ _)
* (_____)(__)(__)(__) |_| |_|
*
*
* Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************/
#include "ObjectTest.hpp"
#include "oatpp/core/macro/codegen.hpp"
#include "oatpp/core/Types.hpp"
namespace oatpp { namespace test { namespace core { namespace data { namespace mapping { namespace type {
namespace {
#include OATPP_CODEGEN_BEGIN(DTO)
class Dto0 : public oatpp::Object {
DTO_INIT(Dto0, Object)
};
class DtoA : public oatpp::Object {
DTO_INIT(DtoA, Object)
DTO_FIELD(String, id);
DTO_HC_EQ(id)
};
class DtoB : public DtoA {
DTO_INIT(DtoB, DtoA)
DTO_FIELD(String, a);
};
class DtoC : public DtoA {
DTO_INIT(DtoC, DtoA)
DTO_FIELD(String, a);
DTO_FIELD(String, b);
DTO_FIELD(String, c);
DTO_HC_EQ(a, b, c);
};
#include OATPP_CODEGEN_END(DTO)
}
void ObjectTest::onRun() {
{
OATPP_LOGI(TAG, "Test 1...");
DtoA::ObjectWrapper a;
OATPP_ASSERT(!a);
OATPP_ASSERT(a == nullptr);
OATPP_ASSERT(a.valueType->classId.id == oatpp::data::mapping::type::__class::AbstractObject::CLASS_ID.id);
OATPP_LOGI(TAG, "OK");
}
{
OATPP_LOGI(TAG, "Test 2...");
DtoA::ObjectWrapper a;
DtoA::ObjectWrapper b;
OATPP_ASSERT(a == b);
OATPP_LOGI(TAG, "OK");
}
{
OATPP_LOGI(TAG, "Test 3...");
auto a = DtoA::createShared();
DtoA::ObjectWrapper b;
OATPP_ASSERT(a != b);
OATPP_ASSERT(b != a);
auto ohc = a->hashCode();
auto whc = std::hash<DtoA::ObjectWrapper>{}(a);
OATPP_ASSERT(ohc == whc);
OATPP_LOGI(TAG, "OK");
}
{
OATPP_LOGI(TAG, "Test 4...");
auto a = Dto0::createShared();
auto b = Dto0::createShared();
OATPP_ASSERT(a != b);
OATPP_ASSERT(a->hashCode() != b->hashCode());
OATPP_LOGI(TAG, "OK");
}
{
OATPP_LOGI(TAG, "Test 5...");
auto a = DtoA::createShared();
auto b = DtoA::createShared();
OATPP_ASSERT(a == b);
OATPP_ASSERT(a->hashCode() == b->hashCode());
a->id = "hello";
OATPP_ASSERT(a != b);
OATPP_ASSERT(a->hashCode() != b->hashCode());
b->id = "hello";
OATPP_ASSERT(a == b);
OATPP_ASSERT(a->hashCode() == b->hashCode());
OATPP_LOGI(TAG, "OK");
}
{
OATPP_LOGI(TAG, "Test 6...");
auto a = DtoB::createShared();
auto b = DtoB::createShared();
a->a = "value1"; // value that is ignored in HC & EQ
a->a = "value2"; // value that is ignored in HC & EQ
OATPP_ASSERT(a == b);
OATPP_ASSERT(a->hashCode() == b->hashCode());
a->id = "hello";
OATPP_ASSERT(a != b);
OATPP_ASSERT(a->hashCode() != b->hashCode());
b->id = "hello";
OATPP_ASSERT(a == b);
OATPP_ASSERT(a->hashCode() == b->hashCode());
OATPP_LOGI(TAG, "OK");
}
{
OATPP_LOGI(TAG, "Test 7...");
auto a = DtoC::createShared();
auto b = DtoC::createShared();
a->id = "1";
b->id = "2";
OATPP_ASSERT(a != b);
OATPP_ASSERT(a->hashCode() != b->hashCode());
a->id = "2";
OATPP_ASSERT(a == b);
OATPP_ASSERT(a->hashCode() == b->hashCode());
a->c = "a";
OATPP_ASSERT(a != b);
OATPP_ASSERT(a->hashCode() != b->hashCode());
b->c = "a";
OATPP_ASSERT(a == b);
OATPP_ASSERT(a->hashCode() == b->hashCode());
OATPP_LOGI(TAG, "OK");
}
{
OATPP_LOGI(TAG, "Test 8...");
auto a = DtoB::createShared();
auto b = DtoB::createShared();
auto c = DtoB::createShared();
auto d = DtoB::createShared();
auto e = DtoB::createShared();
a->a = "1";
b->a = "2";
c->a = "3";
d->a = "4";
e->a = "5";
a->id = "1";
e->id = "1";
oatpp::UnorderedSet<DtoB> set = {a, b, c, d, e};
OATPP_ASSERT(set->size() == 2);
OATPP_ASSERT(set[a] == true);
OATPP_ASSERT(set[b] == true);
OATPP_ASSERT(set[c] == true);
OATPP_ASSERT(set[d] == true);
OATPP_ASSERT(set[e] == true);
OATPP_LOGI(TAG, "OK");
}
}
}}}}}}

View File

@ -0,0 +1,42 @@
/***************************************************************************
*
* Project _____ __ ____ _ _
* ( _ ) /__\ (_ _)_| |_ _| |_
* )(_)( /(__)\ )( (_ _)(_ _)
* (_____)(__)(__)(__) |_| |_|
*
*
* Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************/
#ifndef oatpp_test_core_data_mapping_type_ObjectTest_hpp
#define oatpp_test_core_data_mapping_type_ObjectTest_hpp
#include "oatpp-test/UnitTest.hpp"
namespace oatpp { namespace test { namespace core { namespace data { namespace mapping { namespace type {
class ObjectTest : public UnitTest{
public:
ObjectTest():UnitTest("TEST[core::data::mapping::type::ObjectTest]"){}
void onRun() override;
};
}}}}}}
#endif /* oatpp_test_core_data_mapping_type_ObjectTest_hpp */