GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: src/zserio/ReflectableUtil.h Lines: 72 72 100.0 %
Date: 2023-12-13 14:51:09 Branches: 146 184 79.3 %

Line Branch Exec Source
1
#ifndef ZSERIO_REFLECTABLE_UTIL_H_INC
2
#define ZSERIO_REFLECTABLE_UTIL_H_INC
3
4
#include <algorithm>
5
#include <functional>
6
#include <cmath>
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 : std::integral_constant<bool,
24
        std::is_arithmetic<T>::value ||
25
        std::is_same<StringView, T>::value ||
26
        std::is_enum<T>::value ||
27
        is_bitmask<T>::value>
28
{};
29
30
} // namespace detail
31
32
/**
33
 * Utilities on zserio reflectable interface.
34
 */
35
class ReflectableUtil
36
{
37
public:
38
    /**
39
     * Makes "deep" comparison of given reflectables.
40
     *
41
     * \note Floating point values are compared using "almost equal" strategy.
42
     *
43
     * \param lhs Left-hand side reflectable.
44
     * \param rhs Right-hand side reflectable.
45
     *
46
     * \return True when the reflectables are equal, false otherwise.
47
     */
48
    template <typename ALLOC = std::allocator<uint8_t>>
49
    static bool equal(const IBasicReflectableConstPtr<ALLOC>& lhs,
50
            const IBasicReflectableConstPtr<ALLOC>& rhs);
51
52
    /**
53
     * Gets native value from the given reflectable.
54
     *
55
     * Overload for types where the value is returned by value:
56
     *
57
     * - arithmetic types, enums, bitmasks and strings (via string view).
58
     *
59
     * \param reflectable Reflectable to use for value extraction.
60
     *
61
     * \return Value of the type T.
62
     */
63
    template <typename T, typename ALLOC = std::allocator<uint8_t>,
64
            typename std::enable_if<detail::gets_value_by_value<T>::value, int>::type = 0>
65
32
    static T getValue(const IBasicReflectableConstPtr<ALLOC>& reflectable, const ALLOC& allocator = ALLOC())
66
    {
67







32
        return reflectable->getAnyValue(allocator).template get<T>();
68
    }
69
70
    /**
71
     * Gets constant reference to the native value from the given constant reflectable.
72
     *
73
     * Overload for types where the value is returned by const reference:
74
     *
75
     * - compound, bit buffers and arrays.
76
     *
77
     * \param reflectable Constant reflectable to use for value extraction.
78
     *
79
     * \return Constant reference to the value of the type T.
80
     *
81
     * \throw CppRuntimeException When wrong type is requested ("Bad type in AnyHolder").
82
     */
83
    template <typename T, typename ALLOC = std::allocator<uint8_t>,
84
            typename std::enable_if<!detail::gets_value_by_value<T>::value, int>::type = 0>
85
2
    static const T& getValue(const IBasicReflectableConstPtr<ALLOC>& reflectable,
86
            const ALLOC& allocator = ALLOC())
87
    {
88

2
        return reflectable->getAnyValue(allocator).template get<std::reference_wrapper<const T>>().get();
89
    }
90
91
    /**
92
     * Gets reference to the native value from the given reflectable.
93
     *
94
     * Overload for types where the value is returned by reference:
95
     *
96
     * - compound, arrays.
97
     *
98
     * \param reflectable Reflectable to use for value extraction.
99
     *
100
     * \return Reference to the value of the type T.
101
     *
102
     * \throw CppRuntimeException When wrong type is requested ("Bad type in AnyHolder").
103
     */
104
    template <typename T, typename ALLOC = std::allocator<uint8_t>,
105
            typename std::enable_if<
106
                    !detail::gets_value_by_value<T>::value &&
107
                    !std::is_same<BasicBitBuffer<ALLOC>, T>::value, int>::type = 0>
108
20
    static T& getValue(const IBasicReflectablePtr<ALLOC>& reflectable, const ALLOC& allocator = ALLOC())
109
    {
110


20
        return reflectable->getAnyValue(allocator).template get<std::reference_wrapper<T>>().get();
111
    }
112
113
    /**
114
     * Gets constant reference to the native value from the given reflectable.
115
     *
116
     * Overload for bit buffers which are currently returned only by constant reference.
117
     *
118
     * \param reflectable Reflectable to use for value extraction.
119
     *
120
     * \return Constant reference to the bit buffer value.
121
     *
122
     * \throw CppRuntimeException When wrong type is requested ("Bad type in AnyHolder").
123
     */
124
    template <typename T, typename ALLOC = std::allocator<uint8_t>,
125
            typename std::enable_if<std::is_same<BasicBitBuffer<ALLOC>, T>::value, int>::type = 0>
126
1
    static const T& getValue(const IBasicReflectablePtr<ALLOC>& reflectable, const ALLOC& allocator = ALLOC())
127
    {
128
1
        return reflectable->getAnyValue(allocator).template get<std::reference_wrapper<const T>>().get();
129
    }
130
131
private:
132
    template <typename ALLOC>
133
    static bool arraysEqual(const IBasicReflectableConstPtr<ALLOC>& lhsArray,
134
            const IBasicReflectableConstPtr<ALLOC>& rhsArray);
135
136
    template <typename ALLOC>
137
    static bool compoundsEqual(const IBasicReflectableConstPtr<ALLOC>& lhsCompound,
138
            const IBasicReflectableConstPtr<ALLOC>& rhsCompound);
139
140
    template <typename ALLOC>
141
    static bool valuesEqual(const IBasicReflectableConstPtr<ALLOC>& lhsValue,
142
            const IBasicReflectableConstPtr<ALLOC>& rhsValue);
143
144
    static bool doubleValuesAlmostEqual(double lhs, double rhs);
145
};
146
147
template <typename ALLOC>
148
145
bool ReflectableUtil::equal(const IBasicReflectableConstPtr<ALLOC>& lhs,
149
        const IBasicReflectableConstPtr<ALLOC>& rhs)
150
{
151

145
    if (lhs == nullptr || rhs == nullptr)
152
3
        return lhs == rhs;
153
154
142
    const auto& lhsTypeInfo = lhs->getTypeInfo();
155
142
    const auto& rhsTypeInfo = rhs->getTypeInfo();
156
157

276
    if (lhsTypeInfo.getSchemaType() != rhsTypeInfo.getSchemaType() ||
158
134
            lhsTypeInfo.getSchemaName() != rhsTypeInfo.getSchemaName())
159
9
        return false;
160
161

133
    if (lhs->isArray() || rhs->isArray())
162
    {
163

10
        if (!lhs->isArray() || !rhs->isArray())
164
2
            return false;
165
8
        return arraysEqual<ALLOC>(lhs, rhs);
166
    }
167
123
    else if (TypeInfoUtil::isCompound(lhsTypeInfo.getSchemaType()))
168
    {
169
21
        return compoundsEqual<ALLOC>(lhs, rhs);
170
    }
171
    else
172
    {
173
102
        return valuesEqual<ALLOC>(lhs, rhs);
174
    }
175
}
176
177
template <typename ALLOC>
178
8
bool ReflectableUtil::arraysEqual(const IBasicReflectableConstPtr<ALLOC>& lhsArray,
179
        const IBasicReflectableConstPtr<ALLOC>& rhsArray)
180
{
181
8
    if (lhsArray->size() != rhsArray->size())
182
2
        return false;
183
184
19
    for (size_t i = 0; i < lhsArray->size(); ++i)
185
    {
186

14
        if (!equal<ALLOC>(lhsArray->at(i), rhsArray->at(i)))
187
1
            return false;
188
    }
189
190
5
    return true;
191
}
192
193
template <typename ALLOC>
194
21
bool ReflectableUtil::compoundsEqual(const IBasicReflectableConstPtr<ALLOC>& lhsCompound,
195
        const IBasicReflectableConstPtr<ALLOC>& rhsCompound)
196
{
197
25
    for (const auto& parameterInfo : lhsCompound->getTypeInfo().getParameters())
198
    {
199
9
        auto lhsParameter = lhsCompound->getParameter(parameterInfo.schemaName);
200

9
        auto rhsParameter = rhsCompound->getParameter(parameterInfo.schemaName);
201

5
        if (!equal<ALLOC>(lhsParameter, rhsParameter))
202
1
            return false;
203
    }
204
205
20
    if (TypeInfoUtil::hasChoice(lhsCompound->getTypeInfo().getSchemaType()))
206
    {
207
16
        if (lhsCompound->getChoice() != rhsCompound->getChoice())
208
4
            return false;
209
210
12
        if (!lhsCompound->getChoice().empty())
211
        {
212

19
            auto lhsField = lhsCompound->getField(lhsCompound->getChoice());
213

19
            auto rhsField = rhsCompound->getField(rhsCompound->getChoice());
214

11
            if (!equal<ALLOC>(lhsField, rhsField))
215
3
                return false;
216
        }
217
    }
218
    else
219
    {
220
11
        for (const auto& fieldInfo : lhsCompound->getTypeInfo().getFields())
221
        {
222
15
            auto lhsField = lhsCompound->getField(fieldInfo.schemaName);
223

15
            auto rhsField = rhsCompound->getField(fieldInfo.schemaName);
224

8
            if (!equal<ALLOC>(lhsField, rhsField))
225
1
                return false;
226
        }
227
    }
228
229
12
    return true;
230
}
231
232
template <typename ALLOC>
233
102
bool ReflectableUtil::valuesEqual(const IBasicReflectableConstPtr<ALLOC>& lhsValue,
234
        const IBasicReflectableConstPtr<ALLOC>& rhsValue)
235
{
236
102
    CppType cppType = lhsValue->getTypeInfo().getCppType();
237

102
    if (cppType == CppType::ENUM || cppType == CppType::BITMASK)
238
10
        cppType = lhsValue->getTypeInfo().getUnderlyingType().getCppType();
239
240


102
    switch (cppType)
241
    {
242
    case CppType::BOOL:
243
3
        return lhsValue->getBool() == rhsValue->getBool();
244
    case CppType::INT8:
245
    case CppType::INT16:
246
    case CppType::INT32:
247
    case CppType::INT64:
248
22
        return lhsValue->toInt() == rhsValue->toInt();
249
    case CppType::UINT8:
250
    case CppType::UINT16:
251
    case CppType::UINT32:
252
    case CppType::UINT64:
253
43
        return lhsValue->toUInt() == rhsValue->toUInt();
254
    case CppType::FLOAT:
255
    case CppType::DOUBLE:
256
21
        return doubleValuesAlmostEqual(lhsValue->toDouble(), rhsValue->toDouble());
257
    case CppType::BYTES:
258
        {
259
4
            Span<const uint8_t> lhs = lhsValue->getBytes();
260
4
            Span<const uint8_t> rhs = rhsValue->getBytes();
261
262

4
            return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
263
        }
264
    case CppType::STRING:
265
5
        return lhsValue->getStringView() == rhsValue->getStringView();
266
    case CppType::BIT_BUFFER:
267
3
        return lhsValue->getBitBuffer() == rhsValue->getBitBuffer();
268
    default:
269
1
        throw CppRuntimeException("ReflectableUtil::valuesEqual - Unexpected C++ type!");
270
    }
271
}
272
273
21
inline bool ReflectableUtil::doubleValuesAlmostEqual(double lhs, double rhs)
274
{
275

21
    if (std::isinf(lhs) || std::isinf(rhs))
276



8
        return std::isinf(lhs) && std::isinf(rhs) && ((lhs > 0.0 && rhs > 0.0) || (lhs < 0.0 && rhs < 0.0));
277
278

13
    if (std::isnan(lhs) || std::isnan(rhs))
279

3
        return std::isnan(lhs) && std::isnan(rhs);
280
281
    // see: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon
282
10
    return std::fabs(lhs - rhs) <= std::numeric_limits<double>::epsilon() * std::fabs(lhs + rhs)
283

10
            || std::fabs(lhs - rhs) < std::numeric_limits<double>::min();
284
}
285
286
} // namespace zserio
287
288
#endif // ZSERIO_REFLECTABLE_UTIL_H_INC