1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2019 Couchbase, Inc
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 
18 #include <json_utilities.h>
19 #include <platform/dirutils.h>
20 
21 #include <nlohmann/json.hpp>
22 
23 #include <sys/stat.h>
24 #include <string>
25 #include <sstream>
26 #include <iostream>
27 #include <fstream>
28 #include <map>
29 
30 std::stringstream prototypes;
31 std::stringstream initialization;
32 std::stringstream implementation;
33 
34 typedef std::string (*getValidatorCode)(const std::string&,
35                                         const nlohmann::json&);
36 
37 std::map<std::string, getValidatorCode> validators;
38 std::map<std::string, std::string> datatypes;
39 
getDatatype(const std::string& key, const nlohmann::json& json)40 static std::string getDatatype(const std::string& key,
41                                const nlohmann::json& json) {
42     auto ret = json["type"].get<std::string>();
43     auto iter = datatypes.find(ret);
44     if (iter == datatypes.end()) {
45         std::cerr << "Invalid datatype specified for \"" << key << "\": " << ret
46                   << std::endl;
47         exit(1);
48     }
49 
50     return iter->second;
51 }
52 
getRangeValidatorCode(const std::string& key, const nlohmann::json& json)53 static std::string getRangeValidatorCode(const std::string& key,
54                                          const nlohmann::json& json) {
55     // We've already made the checks to verify that these objects exist
56     auto validator = json["validator"];
57     auto first = validator.begin();
58 
59     auto min = first->find("min");
60     auto max = first->find("max");
61     if (min == first->end() && max == first->end()) {
62         std::cerr << "Incorrect syntax for a range validator specified for"
63                   << "\"" << key << "\"." << std::endl
64                   << "You need at least one of a min or a max clause."
65                   << std::endl;
66         exit(1);
67     }
68 
69     // If min exists and is not a numeric type
70     if (min != first->end() &&
71         !(min->type() == nlohmann::json::value_t::number_integer ||
72           min->type() == nlohmann::json::value_t::number_unsigned ||
73           min->type() == nlohmann::json::value_t::number_float)) {
74         std::cerr << "Incorrect datatype for the range validator specified for "
75                   << "\"" << key << "\"." << std::endl
76                   << "Only numbers are supported." << std::endl;
77         exit(1);
78     }
79 
80     // If max exists and is not of the correct type
81     if (max != first->end() &&
82         !(max->type() == nlohmann::json::value_t::number_integer ||
83           max->type() == nlohmann::json::value_t::number_unsigned ||
84           max->type() == nlohmann::json::value_t::number_float ||
85           (max->type() == nlohmann::json::value_t::string &&
86            max->get<std::string>() == "NUM_CPU"))) {
87         std::cerr << "Incorrect datatype for the range validator specified for "
88                   << "\"" << key << "\"." << std::endl
89                   << "Only numbers are supported." << std::endl;
90         exit(1);
91     }
92 
93     std::string validator_type;
94     std::string mins;
95     std::string maxs;
96 
97     if (getDatatype(key, json) == "float") {
98         validator_type = "FloatRangeValidator";
99         if (min != first->end()) {
100             mins = std::to_string(min->get<float>());
101         } else {
102             mins = "std::numeric_limits<float>::min()";
103         }
104         if (max != first->end()) {
105             maxs = std::to_string(max->get<float>());
106         } else {
107             maxs = "std::numeric_limits<float>::max()";
108         }
109     } else if (getDatatype(key, json) == "ssize_t") {
110         validator_type = "SSizeRangeValidator";
111         if (min != first->end()) {
112             mins = std::to_string(min->get<int64_t>());
113         } else {
114             mins = "std::numeric_limits<ssize_t>::min()";
115         }
116         if (max != first->end()) {
117             maxs = std::to_string(max->get<int64_t>());
118         } else {
119             maxs = "std::numeric_limits<ssize_t>::max()";
120         }
121     } else {
122         validator_type = "SizeRangeValidator";
123         if (min != first->end()) {
124             mins = std::to_string(min->get<uint64_t>());
125         } else {
126             mins = "std::numeric_limits<size_t>::main()";
127         }
128         if (max != first->end() &&
129             max->type() == nlohmann::json::value_t::string &&
130             max->get<std::string>() == "NUM_CPU") {
131             maxs = "Couchbase::get_available_cpu_count()";
132         } else if (max != first->end()) {
133             maxs = std::to_string(max->get<uint64_t>());
134         } else {
135             maxs = "std::numeric_limits<size_t>::max()";
136         }
137     }
138 
139     std::string out = "(new " + validator_type + "())->min(" + mins +
140                       ")->max(" + maxs + ")";
141     return out;
142 }
143 
getEnumValidatorCode(const std::string& key, const nlohmann::json& json)144 static std::string getEnumValidatorCode(const std::string& key,
145                                         const nlohmann::json& json) {
146     // We've already made the checks to verify if these objects exist
147     auto validator = json["validator"];
148     auto first = validator.begin();
149 
150     if (first->type() != nlohmann::json::value_t::array) {
151         std::cerr << "Incorrect enum value for " << key
152                   << ".  Array of values is required." << std::endl;
153         exit(1);
154     }
155 
156     if (first->size() < 1) {
157         std::cerr << "At least one validator enum element is required (" << key
158                   << ")" << std::endl;
159         exit(1);
160     }
161 
162     std::stringstream ss;
163     ss << "(new EnumValidator())";
164 
165     for (auto& obj : *first) {
166         if (obj.type() != nlohmann::json::value_t::string) {
167             std::cerr << "Incorrect validator for " << key
168                       << ", all enum entries must be strings." << std::endl;
169             exit(1);
170         }
171         ss << "\n\t\t->add(\"" << obj.get<std::string>() << "\")";
172     }
173     return ss.str();
174 }
175 
initialize()176 static void initialize() {
177     const char* header = R"(/*
178  *     Copyright 2019 Couchbase, Inc
179  *
180  *   Licensed under the Apache License, Version 2.0 (the "License");
181  *   you may not use this file except in compliance with the License.
182  *   You may obtain a copy of the License at
183  *
184  *       http://www.apache.org/licenses/LICENSE-2.0"
185  *
186  *   Unless required by applicable law or agreed to in writing, software
187  *   distributed under the License is distributed on an "AS IS" BASIS,
188  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189  *   See the License for the specific language governing permissions and
190  *   limitations under the License.
191  */
192 
193 // ###########################################
194 // # DO NOT EDIT! THIS IS A GENERATED FILE
195 // ###########################################
196 )";
197 
198     prototypes << header << R"(
199 #pragma once
200 
201 #include <string>
202 )";
203 
204     implementation << header << R"(
205 #include "configuration.h"
206 #include "configuration_impl.h"
207 #include <platform/sysinfo.h>
208 #include <limits>
209 
210 using namespace std::string_literals;
211 
212 )";
213 
214     validators["range"] = getRangeValidatorCode;
215     validators["enum"] = getEnumValidatorCode;
216     datatypes["bool"] = "bool";
217     datatypes["size_t"] = "size_t";
218     datatypes["ssize_t"] = "ssize_t";
219     datatypes["float"] = "float";
220     datatypes["string"] = "std::string";
221     datatypes["std::string"] = "std::string";
222 }
223 
224 static bool isReadOnly(const nlohmann::json& json) {
225     try {
226         return !cb::jsonGet<bool>(json, "dynamic");
227     } catch (const nlohmann::json::exception& e) {
228         std::cerr << e.what() << std::endl;
229         exit(1);
230     }
231     return false;
232 }
233 
234 static bool hasAliases(const nlohmann::json& json) {
235     auto aliases = json.find("aliases");
236     if (aliases == json.end()) {
237         return false;
238     }
239 
240     if (aliases->type() == nlohmann::json::value_t::string ||
241         aliases->type() == nlohmann::json::value_t::array) {
242         return true;
243     }
244 
245     return false;
246 }
247 
248 static std::vector<std::string> getAliases(const nlohmann::json& json) {
249     auto aliases = json.find("aliases");
250 
251     std::vector<std::string> output;
252 
253     if (aliases->type() == nlohmann::json::value_t::string) {
254         output.emplace_back(aliases->get<std::string>());
255     } else if (aliases->type() == nlohmann::json::value_t::array) {
256         for (auto elem : *aliases) {
257             output.emplace_back(elem.get<std::string>());
258         }
259     }
260 
261     return output;
262 }
263 
264 static std::string getValidator(const std::string& key,
265                                 const nlohmann::json& json) {
266     auto validator = json.find("validator");
267     if (validator == json.end()) {
268         // No validator found
269         return "";
270     }
271 
272     // Abort early if the validator is bad
273     if (validator->size() != 1) {
274         std::cerr << "Only one validator can be specified for " << key
275                   << std::endl;
276         exit(1);
277     }
278 
279     // Get the validator json (first element)
280     auto first = validator->begin();
281 
282     // Lookup the correct function from the map
283     std::map<std::string, getValidatorCode>::iterator iter;
284     iter = validators.find(first.key());
285     if (iter == validators.end()) {
286         std::cerr << "Unknown validator specified for \"" << key << "\": \""
287                   << first->get<std::string>() << "\"" << std::endl;
288         exit(1);
289     }
290 
291     return (iter->second)(key, json);
292 }
293 
294 /**
295  * Generates code from the requirements field.
296  *
297  * Generates code to be used in generated_configuration.cc constructing the
298  * Requirement object and adding the appropriate requirements.
299  * @param key key to generate requirements for
300  * @param json json object representing the config parameter
301  * @param params json object of all parameters, required to determine the
302  * intended type of the required parameter.
303  * @return string of the code constructing a Requirement object.
304  */
305 static std::string getRequirements(const std::string& key,
306                                    const nlohmann::json& json,
307                                    const nlohmann::json& params) {
308     auto requirements = json.find("requires");
309     if (requirements == json.end() || requirements->size() <= 0) {
310         return "";
311     }
312 
313     std::ostringstream ss;
314 
315     ss << "(new Requirement)\n";
316 
317     for (auto req : requirements->items()) {
318         auto reqKey = req.key();
319 
320         auto reqParam = params.find(key);
321         if (reqParam == params.end()) {
322             std::cerr << "Required parameter \"" << reqKey
323                       << "\" for parameter \"" << key << "\" does not exist"
324                       << std::endl;
325             exit(1);
326         }
327 
328         auto type = getDatatype(reqKey, params[reqKey]);
329         std::string value;
330 
331         switch (req.value().type()) {
332         case nlohmann::json::value_t::string:
333             value = std::string("\"") + req.value().get<std::string>() + "\"";
334             break;
335         case nlohmann::json::value_t::number_unsigned:
336             value = std::to_string(req.value().get<uint64_t>());
337             break;
338         case nlohmann::json::value_t::number_integer:
339             value = std::to_string(req.value().get<int64_t>());
340             break;
341         case nlohmann::json::value_t::number_float:
342             value = std::to_string(req.value().get<float_t>());
343             break;
344         case nlohmann::json::value_t::boolean:
345             value = req.value().get<bool>() ? "true" : "false";
346             break;
347         case nlohmann::json::value_t::array:
348         case nlohmann::json::value_t::discarded:
349         case nlohmann::json::value_t::null:
350         case nlohmann::json::value_t::object:
351             break;
352         }
353 
354         ss << "        ->add(\"" << reqKey << "\", (" << type << ")" << value
355            << ")";
356     }
357 
358     return ss.str();
359 }
360 
361 static std::string getGetterPrefix(const std::string& str) {
362     if (str.compare("bool") == 0) {
363         return "is";
364     } else {
365         return "get";
366     }
367 }
368 
369 static std::string getCppName(const std::string& str) {
370     std::stringstream ss;
371     bool doUpper = true;
372 
373     std::string::const_iterator iter;
374     for (iter = str.begin(); iter != str.end(); ++iter) {
375         if (*iter == '_') {
376             doUpper = true;
377         } else {
378             if (doUpper) {
379                 ss << (char)toupper(*iter);
380                 doUpper = false;
381             } else {
382                 ss << (char)*iter;
383             }
384         }
385     }
386     return ss.str();
387 }
388 
389 static void generate(const nlohmann::json& params, const std::string& key) {
390     std::string cppName = getCppName(key);
391 
392     auto json = params[key];
393     std::string type = getDatatype(key, json);
394     std::string defaultVal = json["default"].get<std::string>();
395 
396     if (defaultVal.compare("max") == 0 || defaultVal.compare("min") == 0) {
397         if (type.compare("std::string") != 0) {
398             std::stringstream ss;
399             ss << "std::numeric_limits<" << type << ">::" << defaultVal << "()";
400             defaultVal = ss.str();
401         }
402     }
403 
404     std::string validator = getValidator(key, json);
405     std::string requirements = getRequirements(key, json, params);
406 
407     // Generate prototypes
408     prototypes << "    " << type << " " << getGetterPrefix(type) << cppName
409                << "() const;" << std::endl;
410     const auto dynamic = !isReadOnly(json);
411 
412     if (dynamic) {
413         prototypes << "    void set" << cppName << "(const " << type
414                    << " &nval);" << std::endl;
415     }
416 
417     // Generate initialization code
418     initialization << "    addParameter(\"" << key << "\", " << std::boolalpha;
419     if (type == "std::string") {
420         initialization << "\"" << defaultVal << "\"s, ";
421     } else {
422         initialization << type << "(" << defaultVal << "), ";
423     }
424     initialization << dynamic << ");" << std::endl;
425 
426     if (!validator.empty()) {
427         initialization << "    setValueValidator(\"" << key << "\", "
428                        << validator << ");" << std::endl;
429     }
430     if (!requirements.empty()) {
431         initialization << "    setRequirements(\"" << key << "\", "
432                        << requirements << ");" << std::endl;
433     }
434     if (hasAliases(json)) {
435         for (std::string alias : getAliases(json)) {
436             initialization << "    addAlias(\"" << key << "\", \"" << alias
437                            << "\");" << std::endl;
438         }
439     }
440 
441     // Generate the getter
442     implementation << type << " Configuration::" << getGetterPrefix(type)
443                    << cppName << "() const {" << std::endl
444                    << "    return "
445                    << "getParameter<" << datatypes[type] << ">(\"" << key
446                    << "\");" << std::endl
447                    << "}" << std::endl;
448 
449     if (!isReadOnly(json)) {
450         // generate the setter
451         implementation << "void Configuration::set" << cppName << "(const "
452                        << type << " &nval) {" << std::endl
453                        << "    setParameter(\"" << key << "\", nval);"
454                        << std::endl
455                        << "}" << std::endl;
456     }
457 }
458 
459 /**
460  * Read "configuration.json" and generate getters and setters
461  * for the parameters in there
462  */
463 int main(int argc, char **argv) {
464     if (argc < 4) {
465         std::cerr << "Usage: " << argv[0]
466                   << "<input config file> <header> <source>\n";
467         return 1;
468     }
469 
470     const char* file = argv[1];
471     const char* header = argv[2];
472     const char* source = argv[3];
473 
474     initialize();
475 
476     nlohmann::json json;
477     try {
478         json = nlohmann::json::parse(cb::io::loadFile(file));
479     } catch (const nlohmann::json::exception& e) {
480         std::cerr << "Failed to parse JSON. e.what()=" << e.what() << std::endl;
481         return 1;
482     }
483 
484     auto params = json.find("params");
485     if (params == json.end()) {
486         std::cerr << "FATAL: could not find \"params\" section" << std::endl;
487         return 1;
488     }
489 
490     for (const auto& obj : params->items()) {
491         generate(*params, obj.key());
492     }
493 
494     std::ofstream headerfile(header);
495     headerfile << prototypes.str();
496     headerfile.close();
497 
498     std::ofstream implfile(source);
499     implfile << implementation.str() << std::endl
500              << "void Configuration::initialize() {" << std::endl
501              << initialization.str() << "}" << std::endl;
502     implfile.close();
503     return 0;
504 }
505