d8d14b58ff31db21fe0391d029f11cbac17e896b
[alexxy/gromacs.git] / src / gromacs / options / treesupport.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2016,2017,2018,2019, 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 /*! \internal \file
36  * \brief
37  * Implements functions from treesupport.h.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_options
41  */
42 #include "gmxpre.h"
43
44 #include "treesupport.h"
45
46 #include <set>
47 #include <string>
48 #include <vector>
49
50 #include "gromacs/options/options.h"
51 #include "gromacs/options/optionsassigner.h"
52 #include "gromacs/options/optionsection.h"
53 #include "gromacs/options/optionsvisitor.h"
54 #include "gromacs/utility/exceptions.h"
55 #include "gromacs/utility/ikeyvaluetreeerror.h"
56 #include "gromacs/utility/keyvaluetree.h"
57 #include "gromacs/utility/keyvaluetreebuilder.h"
58 #include "gromacs/utility/stringutil.h"
59
60 namespace gmx
61 {
62
63 namespace
64 {
65
66 class TreeAssignHelper
67 {
68 public:
69     TreeAssignHelper(Options* options, IKeyValueTreeErrorHandler* errorHandler) :
70         assigner_(options),
71         errorHandler_(errorHandler)
72     {
73         if (errorHandler_ == nullptr)
74         {
75             errorHandler_ = defaultKeyValueTreeErrorHandler();
76         }
77     }
78
79     void assignAll(const KeyValueTreeObject& root)
80     {
81         assigner_.start();
82         assignSubTree(root);
83         assigner_.finish();
84     }
85
86 private:
87     void assignSubTree(const KeyValueTreeObject& tree)
88     {
89         // TODO: Use the error handler also in other case.
90         for (const KeyValueTreeProperty& prop : tree.properties())
91         {
92             context_.append(prop.key());
93             if (prop.value().isArray())
94             {
95                 assignArray(prop.key(), prop.value().asArray());
96             }
97             else if (prop.value().isObject())
98             {
99                 assigner_.startSection(prop.key().c_str());
100                 assignSubTree(prop.value().asObject());
101                 assigner_.finishSection();
102             }
103             else
104             {
105                 assigner_.startOption(prop.key().c_str());
106                 try
107                 {
108                     assigner_.appendValue(prop.value().asAny());
109                 }
110                 catch (UserInputError& ex)
111                 {
112                     if (!errorHandler_->onError(&ex, context_))
113                     {
114                         throw;
115                     }
116                 }
117                 assigner_.finishOption();
118             }
119             context_.pop_back();
120         }
121     }
122
123     void assignArray(const std::string& key, const KeyValueTreeArray& array)
124     {
125         if (array.isObjectArray())
126         {
127             for (const KeyValueTreeValue& value : array.values())
128             {
129                 assigner_.startSection(key.c_str());
130                 assignSubTree(value.asObject());
131                 assigner_.finishSection();
132             }
133         }
134         else
135         {
136             assigner_.startOption(key.c_str());
137             for (const KeyValueTreeValue& value : array.values())
138             {
139                 assigner_.appendValue(value.asAny());
140             }
141             assigner_.finishOption();
142         }
143     }
144
145     OptionsAssigner            assigner_;
146     IKeyValueTreeErrorHandler* errorHandler_;
147     KeyValueTreePath           context_;
148 };
149
150 class TreeCheckHelper : private OptionsVisitor
151 {
152 public:
153     TreeCheckHelper(const KeyValueTreeObject& root) :
154         currentObject_(&root),
155         currentKnownNames_(nullptr)
156     {
157     }
158
159     bool                                 hasUnknownPaths() const { return !unknownPaths_.empty(); }
160     const std::vector<KeyValueTreePath>& unknownPaths() const { return unknownPaths_; }
161
162     void processOptionSection(const OptionSectionInfo& section)
163     {
164         OptionsIterator       iterator(section);
165         std::set<std::string> knownNames;
166         currentKnownNames_ = &knownNames;
167         iterator.acceptOptions(this);
168         iterator.acceptSections(this);
169         currentKnownNames_ = nullptr;
170         for (const auto& prop : currentObject_->properties())
171         {
172             if (knownNames.count(prop.key()) == 0)
173             {
174                 unknownPaths_.push_back(currentPath_ + prop.key());
175             }
176         }
177     }
178
179 private:
180     void visitSection(const OptionSectionInfo& section) override
181     {
182         const std::string& name = section.name();
183         if (currentObject_->keyExists(name))
184         {
185             currentKnownNames_->insert(name);
186             auto parentObject     = currentObject_;
187             auto parentKnownNames = currentKnownNames_;
188             // TODO: Consider what to do with mismatching types.
189             currentObject_ = &(*currentObject_)[name].asObject();
190             currentPath_.append(name);
191             processOptionSection(section);
192             currentPath_.pop_back();
193             currentObject_     = parentObject;
194             currentKnownNames_ = parentKnownNames;
195         }
196     }
197     void visitOption(const OptionInfo& option) override
198     {
199         const std::string& name = option.name();
200         if (currentObject_->keyExists(name))
201         {
202             currentKnownNames_->insert(name);
203             // TODO: Consider what to do with mismatching types.
204         }
205     }
206
207     KeyValueTreePath              currentPath_;
208     const KeyValueTreeObject*     currentObject_;
209     std::set<std::string>*        currentKnownNames_;
210     std::vector<KeyValueTreePath> unknownPaths_;
211 };
212
213 class TreeAdjustHelper : private OptionsVisitor
214 {
215 public:
216     TreeAdjustHelper(const KeyValueTreeObject& root, KeyValueTreeBuilder* builder) :
217         currentSourceObject_(&root),
218         currentObjectBuilder_(builder->rootObject())
219     {
220     }
221
222     void processOptionSection(const OptionSectionInfo& section)
223     {
224         OptionsIterator iterator(section);
225         iterator.acceptOptions(this);
226         iterator.acceptSections(this);
227     }
228
229 private:
230     void visitSection(const OptionSectionInfo& section) override
231     {
232         const std::string& name          = section.name();
233         auto               parentBuilder = currentObjectBuilder_;
234         auto               parentObject  = currentSourceObject_;
235         currentObjectBuilder_            = currentObjectBuilder_.addObject(name);
236         currentSourceObject_ = (currentSourceObject_ != nullptr && currentSourceObject_->keyExists(name)
237                                         ? &(*currentSourceObject_)[name].asObject()
238                                         : nullptr);
239         processOptionSection(section);
240         currentSourceObject_  = parentObject;
241         currentObjectBuilder_ = parentBuilder;
242     }
243     void visitOption(const OptionInfo& option) override
244     {
245         const std::string& name = option.name();
246         if (currentSourceObject_ == nullptr || !currentSourceObject_->keyExists(name))
247         {
248             std::vector<Any> values = option.defaultValues();
249             if (values.size() == 1)
250             {
251                 currentObjectBuilder_.addRawValue(name, std::move(values[0]));
252             }
253             else if (values.size() > 1)
254             {
255                 auto arrayBuilder = currentObjectBuilder_.addArray(name);
256                 for (Any& value : values)
257                 {
258                     arrayBuilder.addRawValue(std::move(value));
259                 }
260             }
261         }
262         else
263         {
264             const KeyValueTreeValue& value = (*currentSourceObject_)[name];
265             GMX_RELEASE_ASSERT(!value.isObject(), "Value objects not supported in this context");
266             std::vector<Any> values;
267             if (value.isArray())
268             {
269                 for (const auto& arrayValue : value.asArray().values())
270                 {
271                     GMX_RELEASE_ASSERT(!value.isObject() && !value.isArray(),
272                                        "Complex values not supported in this context");
273                     values.push_back(arrayValue.asAny());
274                 }
275             }
276             else
277             {
278                 values.push_back(value.asAny());
279             }
280             values = option.normalizeValues(values);
281             if (values.empty()) {}
282             else if (values.size() == 1)
283             {
284                 currentObjectBuilder_.addRawValue(name, std::move(values[0]));
285             }
286             else
287             {
288                 auto array = currentObjectBuilder_.addArray(name);
289                 for (auto& arrayValue : values)
290                 {
291                     array.addRawValue(std::move(arrayValue));
292                 }
293             }
294         }
295     }
296
297     const KeyValueTreeObject* currentSourceObject_;
298     KeyValueTreeObjectBuilder currentObjectBuilder_;
299 };
300
301 } // namespace
302
303 //! \cond libapi
304
305 void assignOptionsFromKeyValueTree(Options*                   options,
306                                    const KeyValueTreeObject&  tree,
307                                    IKeyValueTreeErrorHandler* errorHandler)
308 {
309     TreeAssignHelper helper(options, errorHandler);
310     helper.assignAll(tree);
311 }
312
313 void checkForUnknownOptionsInKeyValueTree(const KeyValueTreeObject& tree, const Options& options)
314 {
315     TreeCheckHelper helper(tree);
316     helper.processOptionSection(options.rootSection());
317     if (helper.hasUnknownPaths())
318     {
319         std::string paths(formatAndJoin(helper.unknownPaths(), "\n  ",
320                                         [](const KeyValueTreePath& path) { return path.toString(); }));
321         std::string message("Unknown input values:\n  " + paths);
322         GMX_THROW(InvalidInputError(message));
323     }
324 }
325
326 KeyValueTreeObject adjustKeyValueTreeFromOptions(const KeyValueTreeObject& tree, const Options& options)
327 {
328     KeyValueTreeBuilder builder;
329     TreeAdjustHelper    helper(tree, &builder);
330     helper.processOptionSection(options.rootSection());
331     return builder.build();
332 }
333
334 //! \endcond
335
336 } // namespace gmx