bacc788d34fa4892bf268da062b522443938c24d
[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, 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), errorHandler_(errorHandler)
71         {
72             if (errorHandler_ == nullptr)
73             {
74                 errorHandler_ = defaultKeyValueTreeErrorHandler();
75             }
76         }
77
78         void assignAll(const KeyValueTreeObject &root)
79         {
80             assigner_.start();
81             assignSubTree(root);
82             assigner_.finish();
83         }
84
85     private:
86         void assignSubTree(const KeyValueTreeObject &tree)
87         {
88             // TODO: Use the error handler also in other case.
89             for (const KeyValueTreeProperty &prop : tree.properties())
90             {
91                 context_.append(prop.key());
92                 if (prop.value().isArray())
93                 {
94                     assignArray(prop.key(), prop.value().asArray());
95                 }
96                 else if (prop.value().isObject())
97                 {
98                     assigner_.startSection(prop.key().c_str());
99                     assignSubTree(prop.value().asObject());
100                     assigner_.finishSection();
101                 }
102                 else
103                 {
104                     assigner_.startOption(prop.key().c_str());
105                     try
106                     {
107                         assigner_.appendValue(prop.value().asVariant());
108                     }
109                     catch (UserInputError &ex)
110                     {
111                         if (!errorHandler_->onError(&ex, context_))
112                         {
113                             throw;
114                         }
115                     }
116                     assigner_.finishOption();
117                 }
118                 context_.pop_back();
119             }
120         }
121
122         void assignArray(const std::string       &key,
123                          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.asVariant());
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), currentKnownNames_(nullptr)
155         {
156         }
157
158         bool hasUnknownPaths() const { return !unknownPaths_.empty(); }
159         const std::vector<KeyValueTreePath> &unknownPaths() const
160         {
161             return unknownPaths_;
162         }
163
164         void processOptionSection(const OptionSectionInfo &section)
165         {
166             OptionsIterator       iterator(section);
167             std::set<std::string> knownNames;
168             currentKnownNames_ = &knownNames;
169             iterator.acceptOptions(this);
170             iterator.acceptSections(this);
171             currentKnownNames_ = nullptr;
172             for (const auto &prop : currentObject_->properties())
173             {
174                 if (knownNames.count(prop.key()) == 0)
175                 {
176                     unknownPaths_.push_back(currentPath_ + prop.key());
177                 }
178             }
179         }
180
181     private:
182         void visitSection(const OptionSectionInfo &section) override
183         {
184             const std::string &name = section.name();
185             if (currentObject_->keyExists(name))
186             {
187                 currentKnownNames_->insert(name);
188                 auto parentObject     = currentObject_;
189                 auto parentKnownNames = currentKnownNames_;
190                 // TODO: Consider what to do with mismatching types.
191                 currentObject_ = &(*currentObject_)[name].asObject();
192                 currentPath_.append(name);
193                 processOptionSection(section);
194                 currentPath_.pop_back();
195                 currentObject_     = parentObject;
196                 currentKnownNames_ = parentKnownNames;
197             }
198         }
199         void visitOption(const OptionInfo &option) override
200         {
201             const std::string &name = option.name();
202             if (currentObject_->keyExists(name))
203             {
204                 currentKnownNames_->insert(name);
205                 // TODO: Consider what to do with mismatching types.
206             }
207         }
208
209         KeyValueTreePath               currentPath_;
210         const KeyValueTreeObject      *currentObject_;
211         std::set<std::string>         *currentKnownNames_;
212         std::vector<KeyValueTreePath>  unknownPaths_;
213
214 };
215
216 class TreeAdjustHelper : private OptionsVisitor
217 {
218     public:
219         TreeAdjustHelper(const KeyValueTreeObject &root,
220                          KeyValueTreeBuilder      *builder)
221             : currentSourceObject_(&root),
222               currentObjectBuilder_(builder->rootObject())
223         {
224         }
225
226         void processOptionSection(const OptionSectionInfo &section)
227         {
228             OptionsIterator iterator(section);
229             iterator.acceptOptions(this);
230             iterator.acceptSections(this);
231         }
232
233     private:
234         void visitSection(const OptionSectionInfo &section) override
235         {
236             const std::string &name          = section.name();
237             auto               parentBuilder = currentObjectBuilder_;
238             auto               parentObject  = currentSourceObject_;
239             currentObjectBuilder_ = currentObjectBuilder_.addObject(name);
240             currentSourceObject_  =
241                 (currentSourceObject_ != nullptr && currentSourceObject_->keyExists(name)
242                  ? &(*currentSourceObject_)[name].asObject()
243                  : nullptr);
244             processOptionSection(section);
245             currentSourceObject_  = parentObject;
246             currentObjectBuilder_ = parentBuilder;
247         }
248         void visitOption(const OptionInfo &option) override
249         {
250             const std::string &name = option.name();
251             if (currentSourceObject_ == nullptr || !currentSourceObject_->keyExists(name))
252             {
253                 std::vector<Variant> values = option.defaultValues();
254                 if (values.size() == 1)
255                 {
256                     currentObjectBuilder_.addRawValue(name, std::move(values[0]));
257                 }
258                 else if (values.size() > 1)
259                 {
260                     auto arrayBuilder = currentObjectBuilder_.addArray(name);
261                     for (Variant &value : values)
262                     {
263                         arrayBuilder.addRawValue(std::move(value));
264                     }
265                 }
266             }
267             else
268             {
269                 const KeyValueTreeValue &value = (*currentSourceObject_)[name];
270                 GMX_RELEASE_ASSERT(!value.isObject(), "Value objects not supported in this context");
271                 std::vector<Variant>     values;
272                 if (value.isArray())
273                 {
274                     for (const auto &arrayValue : value.asArray().values())
275                     {
276                         GMX_RELEASE_ASSERT(!value.isObject() && !value.isArray(),
277                                            "Complex values not supported in this context");
278                         values.push_back(arrayValue.asVariant());
279                     }
280                 }
281                 else
282                 {
283                     values.push_back(value.asVariant());
284                 }
285                 values = option.normalizeValues(values);
286                 if (values.empty())
287                 {
288                 }
289                 else if (values.size() == 1)
290                 {
291                     currentObjectBuilder_.addRawValue(name, std::move(values[0]));
292                 }
293                 else
294                 {
295                     auto array = currentObjectBuilder_.addArray(name);
296                     for (auto &arrayValue : values)
297                     {
298                         array.addRawValue(std::move(arrayValue));
299                     }
300                 }
301             }
302         }
303
304         const KeyValueTreeObject  *currentSourceObject_;
305         KeyValueTreeObjectBuilder  currentObjectBuilder_;
306 };
307
308 }   // namespace
309
310 //! \cond libapi
311
312 void assignOptionsFromKeyValueTree(Options                   *options,
313                                    const KeyValueTreeObject  &tree,
314                                    IKeyValueTreeErrorHandler *errorHandler)
315 {
316     TreeAssignHelper helper(options, errorHandler);
317     helper.assignAll(tree);
318 }
319
320 void checkForUnknownOptionsInKeyValueTree(const KeyValueTreeObject &tree,
321                                           const Options            &options)
322 {
323     TreeCheckHelper helper(tree);
324     helper.processOptionSection(options.rootSection());
325     if (helper.hasUnknownPaths())
326     {
327         std::string paths(formatAndJoin(helper.unknownPaths(), "\n  ",
328                                         [](const KeyValueTreePath &path) { return path.toString(); }));
329         std::string message("Unknown input values:\n  " + paths);
330         GMX_THROW(InvalidInputError(message));
331     }
332 }
333
334 KeyValueTreeObject
335 adjustKeyValueTreeFromOptions(const KeyValueTreeObject &tree,
336                               const Options            &options)
337 {
338     KeyValueTreeBuilder builder;
339     TreeAdjustHelper    helper(tree, &builder);
340     helper.processOptionSection(options.rootSection());
341     return builder.build();
342 }
343
344 //! \endcond
345
346 } // namespace gmx