Coverage Report

Created: 2024-04-30 09:35

src/zserio/ReflectableUtil.h
Line
Count
Source (jump to first uncovered line)
1
#ifndef ZSERIO_REFLECTABLE_UTIL_H_INC
2
#define ZSERIO_REFLECTABLE_UTIL_H_INC
3
4
#include <algorithm>
5
#include <cmath>
6
#include <functional>
7
#include <limits>
8
9
#include "zserio/CppRuntimeException.h"
10
#include "zserio/IReflectable.h"
11
#include "zserio/ITypeInfo.h"
12
#include "zserio/StringView.h"
13
#include "zserio/Traits.h"
14
#include "zserio/TypeInfoUtil.h"
15
16
namespace zserio
17
{
18
19
namespace detail
20
{
21
22
template <typename T>
23
struct gets_value_by_value
24
        : std::integral_constant<bool,
25
                  std::is_arithmetic<T>::value || std::is_same<StringView, T>::value ||
26
                          std::is_enum<T>::value || is_bitmask<T>::value>
27
{};
28
29
} // namespace detail
30
31
/**
32
 * Utilities on zserio reflectable interface.
33
 */
34
class ReflectableUtil
35
{
36
public:
37
    /**
38
     * Makes "deep" comparison of given reflectables.
39
     *
40
     * \note Floating point values are compared using "almost equal" strategy.
41
     *
42
     * \param lhs Left-hand side reflectable.
43
     * \param rhs Right-hand side reflectable.
44
     *
45
     * \return True when the reflectables are equal, false otherwise.
46
     */
47
    template <typename ALLOC = std::allocator<uint8_t>>
48
    static bool equal(const IBasicReflectableConstPtr<ALLOC>& lhs, const IBasicReflectableConstPtr<ALLOC>& rhs);
49
50
    /**
51
     * Gets native value from the given reflectable.
52
     *
53
     * Overload for types where the value is returned by value:
54
     *
55
     * - arithmetic types, enums, bitmasks and strings (via string view).
56
     *
57
     * \param reflectable Reflectable to use for value extraction.
58
     *
59
     * \return Value of the type T.
60
     */
61
    template <typename T, typename ALLOC = std::allocator<uint8_t>,
62
            typename std::enable_if<detail::gets_value_by_value<T>::value, int>::type = 0>
63
    static T getValue(const IBasicReflectableConstPtr<ALLOC>& reflectable, const ALLOC& allocator = ALLOC())
64
32
    {
65
32
        return reflectable->getAnyValue(allocator).template get<T>();
66
32
    }
67
68
    /**
69
     * Gets constant reference to the native value from the given constant reflectable.
70
     *
71
     * Overload for types where the value is returned by const reference:
72
     *
73
     * - compound, bit buffers and arrays.
74
     *
75
     * \param reflectable Constant reflectable to use for value extraction.
76
     *
77
     * \return Constant reference to the value of the type T.
78
     *
79
     * \throw CppRuntimeException When wrong type is requested ("Bad type in AnyHolder").
80
     */
81
    template <typename T, typename ALLOC = std::allocator<uint8_t>,
82
            typename std::enable_if<!detail::gets_value_by_value<T>::value, int>::type = 0>
83
    static const T& getValue(
84
            const IBasicReflectableConstPtr<ALLOC>& reflectable, const ALLOC& allocator = ALLOC())
85
2
    {
86
2
        return reflectable->getAnyValue(allocator).template get<std::reference_wrapper<const T>>().get();
87
2
    }
88
89
    /**
90
     * Gets reference to the native value from the given reflectable.
91
     *
92
     * Overload for types where the value is returned by reference:
93
     *
94
     * - compound, arrays.
95
     *
96
     * \param reflectable Reflectable to use for value extraction.
97
     *
98
     * \return Reference to the value of the type T.
99
     *
100
     * \throw CppRuntimeException When wrong type is requested ("Bad type in AnyHolder").
101
     */
102
    template <typename T, typename ALLOC = std::allocator<uint8_t>,
103
            typename std::enable_if<!detail::gets_value_by_value<T>::value &&
104
                            !std::is_same<BasicBitBuffer<ALLOC>, T>::value,
105
                    int>::type = 0>
106
    static T& getValue(const IBasicReflectablePtr<ALLOC>& reflectable, const ALLOC& allocator = ALLOC())
107
20
    {
108
20
        return reflectable->getAnyValue(allocator).template get<std::reference_wrapper<T>>().get();
109
20
    }
110
111
    /**
112
     * Gets constant reference to the native value from the given reflectable.
113
     *
114
     * Overload for bit buffers which are currently returned only by constant reference.
115
     *
116
     * \param reflectable Reflectable to use for value extraction.
117
     *
118
     * \return Constant reference to the bit buffer value.
119
     *
120
     * \throw CppRuntimeException When wrong type is requested ("Bad type in AnyHolder").
121
     */
122
    template <typename T, typename ALLOC = std::allocator<uint8_t>,
123
            typename std::enable_if<std::is_same<BasicBitBuffer<ALLOC>, T>::value, int>::type = 0>
124
    static const T& getValue(const IBasicReflectablePtr<ALLOC>& reflectable, const ALLOC& allocator = ALLOC())
125
1
    {
126
1
        return reflectable->getAnyValue(allocator).template get<std::reference_wrapper<const T>>().get();
127
1
    }
128
129
private:
130
    template <typename ALLOC>
131
    static bool arraysEqual(
132
            const IBasicReflectableConstPtr<ALLOC>& lhsArray, const IBasicReflectableConstPtr<ALLOC>& rhsArray);
133
134
    template <typename ALLOC>
135
    static bool compoundsEqual(const IBasicReflectableConstPtr<ALLOC>& lhsCompound,
136
            const IBasicReflectableConstPtr<ALLOC>& rhsCompound);
137
138
    template <typename ALLOC>
139
    static bool valuesEqual(
140
            const IBasicReflectableConstPtr<ALLOC>& lhsValue, const IBasicReflectableConstPtr<ALLOC>& rhsValue);
141
142
    static bool doubleValuesAlmostEqual(double lhs, double rhs);
143
};
144
145
template <typename ALLOC>
146
bool ReflectableUtil::equal(
147
        const IBasicReflectableConstPtr<ALLOC>& lhs, const IBasicReflectableConstPtr<ALLOC>& rhs)
148
145
{
149
145
    if (lhs == nullptr || 
rhs == nullptr143
)
150
3
        return lhs == rhs;
151
152
142
    const auto& lhsTypeInfo = lhs->getTypeInfo();
153
142
    const auto& rhsTypeInfo = rhs->getTypeInfo();
154
155
142
    if (lhsTypeInfo.getSchemaType() != rhsTypeInfo.getSchemaType() ||
156
142
            
lhsTypeInfo.getSchemaName() != rhsTypeInfo.getSchemaName()134
)
157
9
        return false;
158
159
133
    if (lhs->isArray() || 
rhs->isArray()124
)
160
10
    {
161
10
        if (!lhs->isArray() || 
!rhs->isArray()9
)
162
2
            return false;
163
8
        return arraysEqual<ALLOC>(lhs, rhs);
164
10
    }
165
123
    else if (TypeInfoUtil::isCompound(lhsTypeInfo.getSchemaType()))
166
21
    {
167
21
        return compoundsEqual<ALLOC>(lhs, rhs);
168
21
    }
169
102
    else
170
102
    {
171
102
        return valuesEqual<ALLOC>(lhs, rhs);
172
102
    }
173
133
}
174
175
template <typename ALLOC>
176
bool ReflectableUtil::arraysEqual(
177
        const IBasicReflectableConstPtr<ALLOC>& lhsArray, const IBasicReflectableConstPtr<ALLOC>& rhsArray)
178
8
{
179
8
    if (lhsArray->size() != rhsArray->size())
180
2
        return false;
181
182
19
    
for (size_t i = 0; 6
i < lhsArray->size();
++i13
)
183
14
    {
184
14
        if (!equal<ALLOC>(lhsArray->at(i), rhsArray->at(i)))
185
1
            return false;
186
14
    }
187
188
5
    return true;
189
6
}
190
191
template <typename ALLOC>
192
bool ReflectableUtil::compoundsEqual(const IBasicReflectableConstPtr<ALLOC>& lhsCompound,
193
        const IBasicReflectableConstPtr<ALLOC>& rhsCompound)
194
21
{
195
21
    for (const auto& parameterInfo : lhsCompound->getTypeInfo().getParameters())
196
5
    {
197
5
        auto lhsParameter = lhsCompound->getParameter(parameterInfo.schemaName);
198
5
        auto rhsParameter = rhsCompound->getParameter(parameterInfo.schemaName);
199
5
        if (!equal<ALLOC>(lhsParameter, rhsParameter))
200
1
            return false;
201
5
    }
202
203
20
    if (TypeInfoUtil::hasChoice(lhsCompound->getTypeInfo().getSchemaType()))
204
16
    {
205
16
        if (lhsCompound->getChoice() != rhsCompound->getChoice())
206
4
            return false;
207
208
12
        if (!lhsCompound->getChoice().empty())
209
11
        {
210
11
            auto lhsField = lhsCompound->getField(lhsCompound->getChoice());
211
11
            auto rhsField = rhsCompound->getField(rhsCompound->getChoice());
212
11
            if (!equal<ALLOC>(lhsField, rhsField))
213
3
                return false;
214
11
        }
215
12
    }
216
4
    else
217
4
    {
218
4
        for (const auto& fieldInfo : lhsCompound->getTypeInfo().getFields())
219
8
        {
220
8
            auto lhsField = lhsCompound->getField(fieldInfo.schemaName);
221
8
            auto rhsField = rhsCompound->getField(fieldInfo.schemaName);
222
8
            if (!equal<ALLOC>(lhsField, rhsField))
223
1
                return false;
224
8
        }
225
4
    }
226
227
12
    return true;
228
20
}
229
230
template <typename ALLOC>
231
bool ReflectableUtil::valuesEqual(
232
        const IBasicReflectableConstPtr<ALLOC>& lhsValue, const IBasicReflectableConstPtr<ALLOC>& rhsValue)
233
102
{
234
102
    CppType cppType = lhsValue->getTypeInfo().getCppType();
235
102
    if (cppType == CppType::ENUM || 
cppType == CppType::BITMASK96
)
236
10
        cppType = lhsValue->getTypeInfo().getUnderlyingType().getCppType();
237
238
102
    switch (cppType)
239
102
    {
240
3
    case CppType::BOOL:
241
3
        return lhsValue->getBool() == rhsValue->getBool();
242
10
    case CppType::INT8:
243
14
    case CppType::INT16:
244
18
    case CppType::INT32:
245
22
    case CppType::INT64:
246
22
        return lhsValue->toInt() == rhsValue->toInt();
247
17
    case CppType::UINT8:
248
21
    case CppType::UINT16:
249
39
    case CppType::UINT32:
250
43
    case CppType::UINT64:
251
43
        return lhsValue->toUInt() == rhsValue->toUInt();
252
4
    case CppType::FLOAT:
253
21
    case CppType::DOUBLE:
254
21
        return doubleValuesAlmostEqual(lhsValue->toDouble(), rhsValue->toDouble());
255
4
    case CppType::BYTES:
256
4
        {
257
4
            Span<const uint8_t> lhs = lhsValue->getBytes();
258
4
            Span<const uint8_t> rhs = rhsValue->getBytes();
259
260
4
            return lhs.size() == rhs.size() && 
std::equal(lhs.begin(), lhs.end(), rhs.begin())3
;
261
4
        }
262
5
    case CppType::STRING:
263
5
        return lhsValue->getStringView() == rhsValue->getStringView();
264
3
    case CppType::BIT_BUFFER:
265
3
        return lhsValue->getBitBuffer() == rhsValue->getBitBuffer();
266
1
    default:
267
1
        throw CppRuntimeException("ReflectableUtil::valuesEqual - Unexpected C++ type!");
268
102
    }
269
102
}
270
271
inline bool ReflectableUtil::doubleValuesAlmostEqual(double lhs, double rhs)
272
21
{
273
21
    if (std::isinf(lhs) || 
std::isinf(rhs)15
)
274
8
        return std::isinf(lhs) && 
std::isinf(rhs)6
&&
(4
(4
lhs > 0.04
&&
rhs > 0.02
) ||
(3
lhs < 0.03
&&
rhs < 0.02
));
275
276
13
    if (std::isnan(lhs) || 
std::isnan(rhs)11
)
277
3
        return std::isnan(lhs) && 
std::isnan(rhs)2
;
278
279
    // see: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon
280
10
    return std::fabs(lhs - rhs) <= std::numeric_limits<double>::epsilon() * std::fabs(lhs + rhs) ||
281
10
            
std::fabs(lhs - rhs) < std::numeric_limits<double>::min()4
;
282
13
}
283
284
} // namespace zserio
285
286
#endif // ZSERIO_REFLECTABLE_UTIL_H_INC