1 /*
2  * Copyright 2009-2020 The VOTCA Development Team (http://www.votca.org)
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 
18 // Local VOTCA includes
19 #include "votca/tools/optionshandler.h"
20 #include "votca/tools/propertyiomanipulator.h"
21 #include "votca/tools/tokenizer.h"
22 #include <algorithm>
23 #include <stdexcept>
24 #include <string>
25 
26 namespace votca {
27 namespace tools {
28 
29 template <typename T>
IsValidCast(const tools::Property & prop)30 static bool IsValidCast(const tools::Property &prop) {
31   try {
32     prop.as<T>();
33     return true;
34   } catch (const std::runtime_error &e) {
35     return false;
36   }
37 }
38 
ResolveLinks(Property & prop) const39 void OptionsHandler::ResolveLinks(Property &prop) const {
40 
41   if (prop.hasAttribute("link")) {
42     Tokenizer tok(prop.getAttribute<std::string>("link"), " ,");
43     for (std::string path : tok) {
44       std::string relative_path = "subpackages/" + path;
45       std::string file_path = defaults_path_ + relative_path;
46       tools::Property package;
47       package.LoadFromXML(file_path);
48       const tools::Property &options = *(package.begin());
49       for (Property::const_AttributeIterator attr = options.firstAttribute();
50            attr != options.lastAttribute(); ++attr) {
51         if (!prop.hasAttribute(attr->first)) {
52           prop.setAttribute(attr->first, attr->second);
53         }
54       }
55 
56       for (const auto &child : options) {
57         prop.add(child);
58       }
59     }
60   }
61 
62   for (tools::Property &child : prop) {
63     ResolveLinks(child);
64   }
65 }
66 
ProcessUserInput(const Property & user_input,const std::string & calcname) const67 Property OptionsHandler::ProcessUserInput(const Property &user_input,
68                                           const std::string &calcname) const {
69   Property print = LoadDefaults(calcname);
70 
71   CheckUserInput(user_input, print);
72 
73   OverwriteDefaultsWithUserInput(user_input.get("options"),
74                                  print.get("options"));
75   RemoveOptional(print);
76   CheckRequired(print);
77   InjectDefaultsAsValues(print);
78   RecursivelyCheckOptions(print);
79   return print;
80 }
81 
CheckUserInput(const Property & user_input,const Property & defaults) const82 void OptionsHandler::CheckUserInput(const Property &user_input,
83                                     const Property &defaults) const {
84   if (user_input.hasAttribute("unchecked")) {
85     return;
86   } else {
87     for (const auto &child : user_input) {
88       if (defaults.exists(child.name())) {
89         CheckUserInput(child, defaults.get(child.name()));
90       } else {
91         throw std::runtime_error("Votca has no option:" + child.path() + "." +
92                                  child.name());
93       }
94     }
95   }
96 }
97 
CheckRequired(const Property & options) const98 void OptionsHandler::CheckRequired(const Property &options) const {
99   for (const auto &child : options) {
100     CheckRequired(child);
101   }
102   if (options.hasAttribute("default") &&
103       options.getAttribute<std::string>("default") == "REQUIRED" &&
104       !options.hasAttribute("injected")) {
105     throw std::runtime_error("Please specify an input for:" + options.path() +
106                              "." + options.name());
107   }
108 }
109 
RemoveOptional(Property & options) const110 void OptionsHandler::RemoveOptional(Property &options) const {
111   options.deleteChildren([](const Property &p) {
112     return p.hasAttribute("default") &&
113            p.getAttribute<std::string>("default") == "OPTIONAL" &&
114            !p.hasAttribute("injected");
115   });
116   for (auto &child : options) {
117     RemoveOptional(child);
118   }
119 }
120 
CalculatorOptions(const std::string & calcname) const121 Property OptionsHandler::CalculatorOptions(const std::string &calcname) const {
122   Property print = LoadDefaults(calcname);
123   InjectDefaultsAsValues(print);
124   CleanAttributes(print, {"link"});
125   return print;
126 }
127 
CleanAttributes(Property & options,const std::vector<std::string> & attributes) const128 void OptionsHandler::CleanAttributes(
129     Property &options, const std::vector<std::string> &attributes) const {
130   for (const auto &attribute : attributes) {
131     if (options.hasAttribute(attribute)) {
132       options.deleteAttribute(attribute);
133     }
134   }
135   for (auto &child : options) {
136     CleanAttributes(child, attributes);
137   }
138 }
139 
140 // load the xml description of the calculator (with defaults and test values)
LoadDefaults(const std::string & calculatorname) const141 Property OptionsHandler::LoadDefaults(const std::string &calculatorname) const {
142   Property defaults_all;
143   std::string defaults_file_path =
144       defaults_path_ + "/" + calculatorname + ".xml";
145   defaults_all.LoadFromXML(defaults_file_path);
146   ResolveLinks(defaults_all);
147   return defaults_all;
148 }
149 
InjectDefaultsAsValues(Property & defaults) const150 void OptionsHandler::InjectDefaultsAsValues(Property &defaults) const {
151   for (Property &prop : defaults) {
152     if (prop.HasChildren()) {
153       InjectDefaultsAsValues(prop);
154     } else if (prop.hasAttribute("default") && !prop.hasAttribute("injected")) {
155       std::string value = prop.getAttribute<std::string>("default");
156       if (std::none_of(
157               reserved_keywords_.begin(), reserved_keywords_.end(),
158               [value](const std::string &keyword) { return value == keyword; }))
159         prop.value() = value;
160       ;
161     }
162   }
163 }
164 
OverwriteDefaultsWithUserInput(const Property & user_input,Property & defaults) const165 void OptionsHandler::OverwriteDefaultsWithUserInput(const Property &user_input,
166                                                     Property &defaults) const {
167   // There are 3 distinct cases
168   // a) normal option that can be copied over
169   // b) a list attribute is discovered
170   // c) an unchecked attriute is found,then the whole set is simply copied
171   // over from the user_options, should be done afterwards, as an unchecked
172   // session is not checked and simply copied over
173 
174   if (!defaults.hasAttribute("list")) {
175     defaults.value() = user_input.value();
176     defaults.setAttribute("injected", "true");
177     for (auto &child : defaults) {
178       if (user_input.exists(child.name())) {
179         OverwriteDefaultsWithUserInput(user_input.get(child.name()), child);
180       }
181     }
182   } else {
183     defaults.setAttribute("injected", "true");
184     std::map<std::string, Index> tags;
185     for (const auto &child : defaults) {
186       tags[child.name()]++;
187     }
188 
189     for (const auto &tag : tags) {
190       if (tag.second > 1) {
191         throw std::runtime_error(
192             "Developers: Each distinct tag in list should only appear once.");
193       }
194       std::vector<const tools::Property *> inputs =
195           user_input.Select(tag.first);
196 
197       // if the input has no elements with that tag, remove all children from
198       // default with that tag as well
199       if (inputs.empty()) {
200         defaults.deleteChildren(
201             [&](const Property &prop) { return prop.name() == tag.first; });
202       } else {
203         // copy the element from defaults if the user_input has the element more
204         // than once
205         Property copy = defaults.get(tag.first);
206         for (Index i = 1; i < Index(inputs.size()); i++) {
207           defaults.add(copy);
208         }
209         std::vector<Property *> newdefault_elements =
210             defaults.Select(tag.first);
211         // for each element in both lists do the overwrite again
212         for (Index i = 0; i < Index(inputs.size()); i++) {
213           OverwriteDefaultsWithUserInput(*inputs[i], *newdefault_elements[i]);
214         }
215       }
216     }
217   }
218 
219   if (defaults.hasAttribute("unchecked")) {
220     for (const auto &child : user_input) {
221       defaults.add(child);
222     }
223   }
224 }
225 
GetPropertyChoices(const Property & p)226 std::vector<std::string> OptionsHandler::GetPropertyChoices(const Property &p) {
227   if (p.hasAttribute("choices")) {
228     std::string att = p.getAttribute<std::string>("choices");
229     std::size_t start_bracket = att.find('[');
230     if (start_bracket != std::string::npos) {
231       std::size_t end_bracket = att.find(']');
232       att = att.substr(start_bracket + 1, end_bracket - start_bracket - 1);
233     }
234     return Tokenizer{att, " ,"}.ToVector();
235   } else {
236     return {};
237   }
238 }
239 
RecursivelyCheckOptions(const Property & p) const240 void OptionsHandler::RecursivelyCheckOptions(const Property &p) const {
241 
242   for (const Property &prop : p) {
243     if (prop.HasChildren()) {
244       RecursivelyCheckOptions(prop);
245     } else {
246       std::vector<std::string> choices = GetPropertyChoices(prop);
247       if (choices.empty()) {
248         continue;
249       }
250       if (!IsValidOption(prop, choices)) {
251         std::ostringstream oss;
252         oss << "\nThe input value for \"" << prop.name() << "\"";
253         if (choices.size() == 1) {
254           oss << " should be a \"" << choices.front() << "\"";
255         } else {
256           oss << " should be one of the following values: ";
257           for (const std::string &c : choices) {
258             oss << "\"" << c << "\""
259                 << " ";
260           }
261         }
262         oss << " But \"" << prop.value()
263             << "\" cannot be converted into one.\n";
264         throw std::runtime_error(oss.str());
265       }
266     }
267   }
268 }
269 
IsValidOption(const Property & prop,const std::vector<std::string> & choices) const270 bool OptionsHandler::IsValidOption(
271     const Property &prop, const std::vector<std::string> &choices) const {
272 
273   const std::string &head = choices.front();
274   std::ostringstream oss;
275   bool is_valid = true;
276   if (head == "bool") {
277     is_valid = IsValidCast<bool>(prop);
278   } else if (head == "float") {
279     is_valid = IsValidCast<double>(prop);
280   } else if (head == "float+") {
281     is_valid = IsValidCast<double>(prop) && (prop.as<double>() >= 0.0);
282   } else if (head == "int") {
283     is_valid = IsValidCast<Index>(prop);
284   } else if (head == "int+") {
285     is_valid = IsValidCast<Index>(prop) && (prop.as<Index>() >= 0);
286   } else {
287     std::string value = prop.as<std::string>();
288     std::string att = prop.getAttribute<std::string>("choices");
289     std::size_t start_bracket = att.find('[');
290     if (start_bracket == std::string::npos) {
291       // There is a single choice out of multiple default valid choices
292       auto it = std::find(std::cbegin(choices), std::cend(choices), value);
293       is_valid = (it != std::cend(choices));
294     } else {
295       // there are multiple valid choices
296       Tokenizer tok{value, " ,"};
297       for (const std::string &word : tok) {
298         auto it = std::find(std::cbegin(choices), std::cend(choices), word);
299         if (it == std::cend(choices)) {
300           is_valid = false;
301           break;
302         }
303       }
304     }
305   }
306   if (std::find(additional_choices_.begin(), additional_choices_.end(),
307                 prop.as<std::string>()) != additional_choices_.end()) {
308     is_valid = true;
309   }
310   return is_valid;
311 }
312 
313 }  // namespace tools
314 }  // namespace votca
315