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