Apply clang-format-11
[alexxy/gromacs.git] / src / gromacs / utility / keyvaluetree.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2016,2017,2018,2019,2020,2021, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 #include "gmxpre.h"
36
37 #include "keyvaluetree.h"
38
39 #include <string>
40 #include <vector>
41
42 #include "gromacs/utility/compare.h"
43 #include "gromacs/utility/gmxassert.h"
44 #include "gromacs/utility/strconvert.h"
45 #include "gromacs/utility/stringutil.h"
46 #include "gromacs/utility/textwriter.h"
47
48 namespace gmx
49 {
50
51 namespace
52 {
53
54 //! Helper function to split a KeyValueTreePath to its components
55 std::vector<std::string> splitPathElements(const std::string& path)
56 {
57     GMX_ASSERT(!path.empty() && path[0] == '/', "Paths to KeyValueTree should start with '/'");
58     return splitDelimitedString(path.substr(1), '/');
59 }
60
61 } // namespace
62
63 /********************************************************************
64  * KeyValueTreePath
65  */
66
67 KeyValueTreePath::KeyValueTreePath(const char* path) : path_(splitPathElements(path)) {}
68
69 KeyValueTreePath::KeyValueTreePath(const std::string& path) : path_(splitPathElements(path)) {}
70
71 std::string KeyValueTreePath::toString() const
72 {
73     return "/" + joinStrings(path_, "/");
74 }
75
76 /********************************************************************
77  * KeyValueTreeObject
78  */
79
80 bool KeyValueTreeObject::hasDistinctProperties(const KeyValueTreeObject& obj) const
81 {
82     for (const auto& prop : obj.values_)
83     {
84         if (keyExists(prop.key()))
85         {
86             GMX_RELEASE_ASSERT(!prop.value().isArray(), "Comparison of arrays not implemented");
87             if (prop.value().isObject() && valueMap_.at(prop.key()).isObject())
88             {
89                 return valueMap_.at(prop.key()).asObject().hasDistinctProperties(prop.value().asObject());
90             }
91             return false;
92         }
93     }
94     return true;
95 }
96
97 /********************************************************************
98  * Key value tree dump
99  */
100
101 //! \cond libapi
102 void dumpKeyValueTree(TextWriter* writer, const KeyValueTreeObject& tree)
103 {
104     for (const auto& prop : tree.properties())
105     {
106         const auto& value = prop.value();
107         if (value.isObject())
108         {
109             writer->writeString(prop.key());
110             writer->writeLine(":");
111             int oldIndent = writer->wrapperSettings().indent();
112             writer->wrapperSettings().setIndent(oldIndent + 2);
113             dumpKeyValueTree(writer, value.asObject());
114             writer->wrapperSettings().setIndent(oldIndent);
115         }
116         else if (value.isArray()
117                  && std::all_of(value.asArray().values().begin(),
118                                 value.asArray().values().end(),
119                                 [](const auto& elem) { return elem.isObject(); }))
120         {
121             // Array containing only objects
122             writer->writeString(prop.key());
123             writer->writeLine(":");
124             int oldIndent = writer->wrapperSettings().indent();
125             writer->wrapperSettings().setIndent(oldIndent + 2);
126             for (const auto& elem : value.asArray().values())
127             {
128                 dumpKeyValueTree(writer, elem.asObject());
129             }
130             writer->wrapperSettings().setIndent(oldIndent);
131         }
132         else
133         {
134             int indent = writer->wrapperSettings().indent();
135             writer->writeString(formatString("%*s", -(33 - indent), prop.key().c_str()));
136             writer->writeString(" = ");
137             if (value.isArray())
138             {
139                 writer->writeString("[");
140                 for (const auto& elem : value.asArray().values())
141                 {
142                     GMX_RELEASE_ASSERT(
143                             !elem.isObject() && !elem.isArray(),
144                             "Only arrays of simple types and array of objects are implemented. "
145                             "Arrays of arrays and mixed arrays are not supported.");
146                     writer->writeString(" ");
147                     writer->writeString(simpleValueToString(elem));
148                 }
149                 writer->writeString(" ]");
150             }
151             else
152             {
153                 writer->writeString(simpleValueToString(value));
154             }
155             writer->writeLine();
156         }
157     }
158 }
159 //! \endcond
160
161 /********************************************************************
162  * Key value tree comparison
163  */
164
165 namespace
166 {
167
168 class CompareHelper
169 {
170 public:
171     CompareHelper(TextWriter* writer, real ftol, real abstol) :
172         writer_(writer), ftol_(ftol), abstol_(abstol)
173     {
174     }
175     void compareObjects(const KeyValueTreeObject& obj1, const KeyValueTreeObject& obj2)
176     {
177         for (const auto& prop1 : obj1.properties())
178         {
179             currentPath_.append(prop1.key());
180             if (obj2.keyExists(prop1.key()))
181             {
182                 compareValues(prop1.value(), obj2[prop1.key()]);
183             }
184             else
185             {
186                 handleMissingKeyInSecondObject(prop1.value());
187             }
188             currentPath_.pop_back();
189         }
190         for (const auto& prop2 : obj2.properties())
191         {
192             currentPath_.append(prop2.key());
193             if (!obj1.keyExists(prop2.key()))
194             {
195                 handleMissingKeyInFirstObject(prop2.value());
196             }
197             currentPath_.pop_back();
198         }
199     }
200
201 private:
202     void compareValues(const KeyValueTreeValue& value1, const KeyValueTreeValue& value2)
203     {
204         if (value1.type() == value2.type())
205         {
206             if (value1.isObject())
207             {
208                 compareObjects(value1.asObject(), value2.asObject());
209             }
210             else if (value1.isArray())
211             {
212                 GMX_RELEASE_ASSERT(false, "Array comparison not implemented");
213             }
214             else if (!areSimpleValuesOfSameTypeEqual(value1, value2))
215             {
216                 writer_->writeString(currentPath_.toString());
217                 writer_->writeLine(formatString(" (%s - %s)",
218                                                 simpleValueToString(value1).c_str(),
219                                                 simpleValueToString(value2).c_str()));
220             }
221         }
222         else if ((value1.isType<double>() && value2.isType<float>())
223                  || (value1.isType<float>() && value2.isType<double>()))
224         {
225             const bool  firstIsDouble = value1.isType<double>();
226             const float v1 = firstIsDouble ? value1.cast<double>() : value1.cast<float>();
227             const float v2 = firstIsDouble ? value2.cast<float>() : value2.cast<double>();
228             if (!equal_float(v1, v2, ftol_, abstol_))
229             {
230                 writer_->writeString(currentPath_.toString());
231                 writer_->writeLine(formatString(" (%e - %e)", v1, v2));
232             }
233         }
234         else
235         {
236             handleMismatchingTypes(value1, value2);
237         }
238     }
239
240     bool areSimpleValuesOfSameTypeEqual(const KeyValueTreeValue& value1, const KeyValueTreeValue& value2) const
241     {
242         GMX_ASSERT(value1.type() == value2.type(), "Caller should ensure that types are equal");
243         if (value1.isType<bool>())
244         {
245             return value1.cast<bool>() == value2.cast<bool>();
246         }
247         else if (value1.isType<int>())
248         {
249             return value1.cast<int>() == value2.cast<int>();
250         }
251         else if (value1.isType<int64_t>())
252         {
253             return value1.cast<int64_t>() == value2.cast<int64_t>();
254         }
255         else if (value1.isType<double>())
256         {
257             return equal_double(value1.cast<double>(), value2.cast<double>(), ftol_, abstol_);
258         }
259         else if (value1.isType<float>())
260         {
261             return equal_float(value1.cast<float>(), value2.cast<float>(), ftol_, abstol_);
262         }
263         else if (value1.isType<std::string>())
264         {
265             return value1.cast<std::string>() == value2.cast<std::string>();
266         }
267         else
268         {
269             GMX_RELEASE_ASSERT(false, "Unknown value type");
270             return false;
271         }
272     }
273
274     void handleMismatchingTypes(const KeyValueTreeValue& /* value1 */,
275                                 const KeyValueTreeValue& /* value2 */)
276     {
277         writer_->writeString(currentPath_.toString());
278         writer_->writeString(" type mismatch");
279     }
280
281     void handleMissingKeyInFirstObject(const KeyValueTreeValue& value)
282     {
283         const std::string message = formatString("%s (missing - %s)",
284                                                  currentPath_.toString().c_str(),
285                                                  formatValueForMissingMessage(value).c_str());
286         writer_->writeLine(message);
287     }
288     void handleMissingKeyInSecondObject(const KeyValueTreeValue& value)
289     {
290         const std::string message = formatString("%s (%s - missing)",
291                                                  currentPath_.toString().c_str(),
292                                                  formatValueForMissingMessage(value).c_str());
293         writer_->writeLine(message);
294     }
295
296     static std::string formatValueForMissingMessage(const KeyValueTreeValue& value)
297     {
298         if (value.isObject() || value.isArray())
299         {
300             return "present";
301         }
302         return simpleValueToString(value);
303     }
304
305     KeyValueTreePath currentPath_;
306     TextWriter*      writer_;
307     real             ftol_;
308     real             abstol_;
309 };
310
311 } // namespace
312
313 //! \cond libapi
314 void compareKeyValueTrees(TextWriter*               writer,
315                           const KeyValueTreeObject& tree1,
316                           const KeyValueTreeObject& tree2,
317                           real                      ftol,
318                           real                      abstol)
319 {
320     CompareHelper helper(writer, ftol, abstol);
321     helper.compareObjects(tree1, tree2);
322 }
323 //! \endcond
324
325 } // namespace gmx