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 |