2 * This file is part of the GROMACS molecular simulation package.
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.
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.
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.
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.
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.
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.
37 #include "keyvaluetree.h"
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"
54 //! Helper function to split a KeyValueTreePath to its components
55 std::vector<std::string> splitPathElements(const std::string& path)
57 GMX_ASSERT(!path.empty() && path[0] == '/', "Paths to KeyValueTree should start with '/'");
58 return splitDelimitedString(path.substr(1), '/');
63 /********************************************************************
67 KeyValueTreePath::KeyValueTreePath(const char* path) : path_(splitPathElements(path)) {}
69 KeyValueTreePath::KeyValueTreePath(const std::string& path) : path_(splitPathElements(path)) {}
71 std::string KeyValueTreePath::toString() const
73 return "/" + joinStrings(path_, "/");
76 /********************************************************************
80 bool KeyValueTreeObject::hasDistinctProperties(const KeyValueTreeObject& obj) const
82 for (const auto& prop : obj.values_)
84 if (keyExists(prop.key()))
86 GMX_RELEASE_ASSERT(!prop.value().isArray(), "Comparison of arrays not implemented");
87 if (prop.value().isObject() && valueMap_.at(prop.key()).isObject())
89 return valueMap_.at(prop.key()).asObject().hasDistinctProperties(prop.value().asObject());
97 /********************************************************************
102 void dumpKeyValueTree(TextWriter* writer, const KeyValueTreeObject& tree)
104 for (const auto& prop : tree.properties())
106 const auto& value = prop.value();
107 if (value.isObject())
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);
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(); }))
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())
128 dumpKeyValueTree(writer, elem.asObject());
130 writer->wrapperSettings().setIndent(oldIndent);
134 int indent = writer->wrapperSettings().indent();
135 writer->writeString(formatString("%*s", -(33 - indent), prop.key().c_str()));
136 writer->writeString(" = ");
139 writer->writeString("[");
140 for (const auto& elem : value.asArray().values())
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));
149 writer->writeString(" ]");
153 writer->writeString(simpleValueToString(value));
161 /********************************************************************
162 * Key value tree comparison
171 CompareHelper(TextWriter* writer, real ftol, real abstol) :
172 writer_(writer), ftol_(ftol), abstol_(abstol)
175 void compareObjects(const KeyValueTreeObject& obj1, const KeyValueTreeObject& obj2)
177 for (const auto& prop1 : obj1.properties())
179 currentPath_.append(prop1.key());
180 if (obj2.keyExists(prop1.key()))
182 compareValues(prop1.value(), obj2[prop1.key()]);
186 handleMissingKeyInSecondObject(prop1.value());
188 currentPath_.pop_back();
190 for (const auto& prop2 : obj2.properties())
192 currentPath_.append(prop2.key());
193 if (!obj1.keyExists(prop2.key()))
195 handleMissingKeyInFirstObject(prop2.value());
197 currentPath_.pop_back();
202 void compareValues(const KeyValueTreeValue& value1, const KeyValueTreeValue& value2)
204 if (value1.type() == value2.type())
206 if (value1.isObject())
208 compareObjects(value1.asObject(), value2.asObject());
210 else if (value1.isArray())
212 GMX_RELEASE_ASSERT(false, "Array comparison not implemented");
214 else if (!areSimpleValuesOfSameTypeEqual(value1, value2))
216 writer_->writeString(currentPath_.toString());
217 writer_->writeLine(formatString(" (%s - %s)",
218 simpleValueToString(value1).c_str(),
219 simpleValueToString(value2).c_str()));
222 else if ((value1.isType<double>() && value2.isType<float>())
223 || (value1.isType<float>() && value2.isType<double>()))
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_))
230 writer_->writeString(currentPath_.toString());
231 writer_->writeLine(formatString(" (%e - %e)", v1, v2));
236 handleMismatchingTypes(value1, value2);
240 bool areSimpleValuesOfSameTypeEqual(const KeyValueTreeValue& value1, const KeyValueTreeValue& value2) const
242 GMX_ASSERT(value1.type() == value2.type(), "Caller should ensure that types are equal");
243 if (value1.isType<bool>())
245 return value1.cast<bool>() == value2.cast<bool>();
247 else if (value1.isType<int>())
249 return value1.cast<int>() == value2.cast<int>();
251 else if (value1.isType<int64_t>())
253 return value1.cast<int64_t>() == value2.cast<int64_t>();
255 else if (value1.isType<double>())
257 return equal_double(value1.cast<double>(), value2.cast<double>(), ftol_, abstol_);
259 else if (value1.isType<float>())
261 return equal_float(value1.cast<float>(), value2.cast<float>(), ftol_, abstol_);
263 else if (value1.isType<std::string>())
265 return value1.cast<std::string>() == value2.cast<std::string>();
269 GMX_RELEASE_ASSERT(false, "Unknown value type");
274 void handleMismatchingTypes(const KeyValueTreeValue& /* value1 */,
275 const KeyValueTreeValue& /* value2 */)
277 writer_->writeString(currentPath_.toString());
278 writer_->writeString(" type mismatch");
281 void handleMissingKeyInFirstObject(const KeyValueTreeValue& value)
283 const std::string message = formatString("%s (missing - %s)",
284 currentPath_.toString().c_str(),
285 formatValueForMissingMessage(value).c_str());
286 writer_->writeLine(message);
288 void handleMissingKeyInSecondObject(const KeyValueTreeValue& value)
290 const std::string message = formatString("%s (%s - missing)",
291 currentPath_.toString().c_str(),
292 formatValueForMissingMessage(value).c_str());
293 writer_->writeLine(message);
296 static std::string formatValueForMissingMessage(const KeyValueTreeValue& value)
298 if (value.isObject() || value.isArray())
302 return simpleValueToString(value);
305 KeyValueTreePath currentPath_;
314 void compareKeyValueTrees(TextWriter* writer,
315 const KeyValueTreeObject& tree1,
316 const KeyValueTreeObject& tree2,
320 CompareHelper helper(writer, ftol, abstol);
321 helper.compareObjects(tree1, tree2);