tConfigJSON.cc - pism - [fork] customized build of PISM, the parallel ice sheet model (tillflux branch)
(HTM) git clone git://src.adamsgaard.dk/pism
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) LICENSE
---
tConfigJSON.cc (10722B)
---
1 /* Copyright (C) 2014, 2016, 2018 PISM Authors
2 *
3 * This file is part of PISM.
4 *
5 * PISM is free software; you can redistribute it and/or modify it under the
6 * terms of the GNU General Public License as published by the Free Software
7 * Foundation; either version 3 of the License, or (at your option) any later
8 * version.
9 *
10 * PISM is distributed in the hope that it will be useful, but WITHOUT ANY
11 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13 * details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with PISM; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include <vector>
21 #include <cstdlib> // free
22
23 #include "ConfigJSON.hh"
24 #include "error_handling.hh"
25 #include "pism_utilities.hh"
26 #include "io/File.hh"
27
28 namespace pism {
29
30 /*! Given a 'path' "alice.bob.charlie", look for the JSON object 'bob'
31 * containing a key 'charlie' in the object 'alice'. Use the 'object'
32 * argument as the root.
33 *
34 * In other words, 'path' describes a node of a tree with 'object' as
35 * the root, and this function returns the pointer to the node if
36 * found, and NULL otherwise.
37 */
38 static json_t* find_json_value(json_t *root, const std::string &name) {
39 if (root == NULL) {
40 return NULL;
41 }
42
43 json_t *object = root;
44 for (auto object_name : split(name, '.')) {
45
46 object = json_object_get(object, object_name.c_str());
47
48 if (object == NULL) {
49 break;
50 }
51 }
52
53 return object;
54 }
55
56 /*!
57 * Convert an STL vector to a JSON array.
58 */
59 static json_t* pack_json_array(const std::vector<double> &data) {
60 json_t *array = json_array();
61 if (array == NULL) {
62 throw RuntimeError::formatted(PISM_ERROR_LOCATION,
63 "failed to create an empty JSON array");
64 }
65
66 for (const auto &v : data) {
67 json_t *value = json_pack("f", v);
68 if (value == NULL) {
69 throw RuntimeError::formatted(PISM_ERROR_LOCATION,
70 "failed to pack a JSON number");
71 }
72 if (json_array_append_new(array, value) != 0) {
73 throw RuntimeError::formatted(PISM_ERROR_LOCATION,
74 "failed to add an element to a JSON array");
75 }
76 }
77
78 return array;
79 }
80
81 /*!
82 * Convert a JSON array to an STL vector.
83 */
84 std::vector<double> unpack_json_array(const char *name,
85 const json_t *input) {
86 std::vector<double> result;
87
88 if (json_typeof(input) != JSON_ARRAY) {
89 throw RuntimeError::formatted(PISM_ERROR_LOCATION,
90 "%s is not an array", name);
91 }
92
93 size_t N = json_array_size(input);
94
95 for (size_t k = 0; k < N; ++k) {
96 json_t *value = json_array_get(input, k);
97 if (value == NULL) {
98 throw RuntimeError::formatted(PISM_ERROR_LOCATION,
99 "failed to get an element of %s",
100 name);
101 }
102
103 double v = 0.0;
104 if (json_unpack(value, "F", &v) == 0) {
105 result.push_back(v);
106 } else {
107 throw RuntimeError::formatted(PISM_ERROR_LOCATION,
108 "failed to convert an element of %s to double",
109 name);
110 }
111 }
112
113 return result;
114 }
115
116
117 template<typename PISMType, typename TMPType>
118 static void get_all_values(json_t *root, const std::string &path,
119 int type, const char *fmt, std::map<std::string,PISMType> &accum) {
120 const char *key;
121 json_t *value;
122
123 json_object_foreach(root, key, value) {
124 std::string parameter = path + key;
125 int value_type = json_typeof(value);
126 if (value_type == type) {
127 TMPType tmp;
128 if (json_unpack(value, fmt, &tmp) == 0) {
129 accum[parameter] = tmp;
130 } else {
131 throw RuntimeError::formatted(PISM_ERROR_LOCATION,
132 "failed to json_unpack %s using format %s",
133 parameter.c_str(), fmt);
134 }
135 } else if (value_type == JSON_OBJECT) {
136 get_all_values<PISMType, TMPType>(value, parameter + ".", type, fmt, accum);
137 }
138 }
139 }
140
141 static void get_all_arrays(json_t *root, const std::string &path,
142 std::map<std::string, std::vector<double> > &accum) {
143 const char *key;
144 json_t *value;
145
146 json_object_foreach(root, key, value) {
147 std::string parameter = path + key;
148
149 switch (json_typeof(value)) {
150 case JSON_ARRAY:
151 accum[parameter] = unpack_json_array(parameter.c_str(), value);
152 break;
153 case JSON_OBJECT:
154 get_all_arrays(value, parameter + ".", accum);
155 break;
156 default:
157 break;
158 }
159 }
160 }
161
162 template<typename PISMType, typename TMPType>
163 static PISMType get_value(json_t *object, const std::string &name,
164 const char *fmt, const char *type_name) {
165 json_t *value = find_json_value(object, name);
166 if (value == NULL) {
167 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "%s was not found", name.c_str());
168 }
169
170 TMPType tmp;
171 if (json_unpack(value, fmt, &tmp) == 0) {
172 return tmp;
173 } else {
174 throw RuntimeError::formatted(PISM_ERROR_LOCATION,
175 "failed to convert %s to a %s", name.c_str(), type_name);
176 }
177 }
178
179 /*! Store a 'value' corresponding to the key 'name' in the database 'data'.
180 *
181 * If a name refers to an object "alice.bob", the object "alice" must
182 * exist in the tree already, but "bob" may not exist before this call
183 * and will be created. In other words, this method allows adding new
184 * leaves only.
185 */
186 static void set_value(json_t *data, const std::string &name, json_t *value) {
187 std::vector<std::string> path = split(name, '.');
188 if (path.size() == 0) {
189 // stop if 'name' is empty
190 return;
191 }
192
193 std::string key = path.back();
194 path.pop_back();
195
196 json_t *object = NULL;
197 if (path.empty()) {
198 object = data;
199 } else {
200 object = find_json_value(data, join(path, "."));
201 }
202
203 if (object != NULL) {
204 if (json_is_object(object)) {
205 json_object_set_new(object, key.c_str(), value);
206 } else {
207 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "cannot set %s: %s is not an object",
208 name.c_str(), join(path, ".").c_str());
209 }
210 } else {
211 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "cannot set %s: %s is not found",
212 name.c_str(), join(path, ".").c_str());
213 }
214 }
215
216
217 ConfigJSON::ConfigJSON(units::System::Ptr unit_system)
218 : Config(unit_system) {
219 m_data = NULL;
220 init_from_string("{}");
221 }
222
223 ConfigJSON::~ConfigJSON() {
224 json_decref(m_data);
225 }
226
227 /*! Initialize the database by reading from a file 'filename'.
228 */
229 void ConfigJSON::init_from_file(const std::string &filename) {
230
231 // free existing data if present
232 if (m_data != NULL) {
233 json_decref(m_data);
234 }
235
236 json_error_t error;
237 m_data = json_load_file(filename.c_str(), JSON_DECODE_INT_AS_REAL, &error);
238
239 if (m_data == NULL) {
240 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "Error loading config from '%s'"
241 " at line %d, column %d.",
242 filename.c_str(), error.line, error.column);
243 }
244 }
245
246 /*! Initialize the database using a string 'string'.
247 */
248 void ConfigJSON::init_from_string(const std::string &string) {
249 // free existing data if present
250 if (m_data != NULL) {
251 json_decref(m_data);
252 }
253
254 json_error_t error;
255 m_data = json_loads(string.c_str(), JSON_DECODE_INT_AS_REAL, &error);
256
257 if (m_data == NULL) {
258 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "Error loading config from '%s'"
259 " at line %d, column %d.",
260 string.c_str(), error.line, error.column);
261 }
262 }
263
264 /*! Return the JSON string representation of the configuration database.
265 */
266 std::string ConfigJSON::dump() const {
267 std::string result;
268
269 char *tmp = json_dumps(m_data, JSON_INDENT(2) | JSON_ENSURE_ASCII | JSON_SORT_KEYS);
270 if (tmp != NULL) {
271 result = tmp;
272 free(tmp);
273 }
274
275 return result;
276 }
277
278 Config::Doubles ConfigJSON::all_doubles_impl() const {
279 Config::Doubles result;
280
281 std::map<std::string, double> scalars;
282 get_all_values<double, double>(m_data, "", JSON_REAL, "F", scalars);
283
284 for (const auto &p : scalars) {
285 result[p.first] = {p.second};
286 }
287
288 get_all_arrays(m_data, "", result);
289
290 return result;
291 }
292
293 Config::Strings ConfigJSON::all_strings_impl() const {
294 Config::Strings result;
295 get_all_values<std::string, const char*>(m_data, "", JSON_STRING, "s", result);
296 return result;
297 }
298
299 Config::Flags ConfigJSON::all_flags_impl() const {
300 Config::Flags result;
301 get_all_values<bool, int>(m_data, "", JSON_TRUE, "b", result);
302 get_all_values<bool, int>(m_data, "", JSON_FALSE, "b", result);
303 return result;
304 }
305
306 void ConfigJSON::set_number_impl(const std::string &name, double value) {
307 set_value(m_data, name, json_pack("f", value));
308 }
309
310 void ConfigJSON::set_numbers_impl(const std::string &name,
311 const std::vector<double> &values) {
312 set_value(m_data, name, pack_json_array(values));
313 }
314
315 void ConfigJSON::set_flag_impl(const std::string &name, bool value) {
316 set_value(m_data, name, json_pack("b", value));
317 }
318
319 void ConfigJSON::set_string_impl(const std::string &name, const std::string &value) {
320 set_value(m_data, name, json_pack("s", value.c_str()));
321 }
322
323 double ConfigJSON::get_number_impl(const std::string &name) const {
324 return get_value<double, double>(m_data, name, "F", "number");
325 }
326
327 std::vector<double> ConfigJSON::get_numbers_impl(const std::string &name) const {
328 json_t *value = find_json_value(m_data, name);
329
330 if (value == NULL) {
331 throw RuntimeError::formatted(PISM_ERROR_LOCATION,
332 "%s was not found", name.c_str());
333 }
334
335 return unpack_json_array(name.c_str(), value);
336 }
337
338 std::string ConfigJSON::get_string_impl(const std::string &name) const {
339 return get_value<std::string, const char *>(m_data, name, "s", "string");
340 }
341
342 bool ConfigJSON::get_flag_impl(const std::string &name) const {
343 return get_value<bool, int>(m_data, name, "b", "flag");
344 }
345
346 void ConfigJSON::read_impl(const File &nc) {
347 std::string config_string = nc.read_text_attribute("PISM_GLOBAL", "pism_config");
348 this->init_from_string(config_string);
349 }
350
351 void ConfigJSON::write_impl(const File &nc) const {
352 nc.write_attribute("PISM_GLOBAL", "pism_config", this->dump());
353 }
354
355 bool ConfigJSON::is_set_impl(const std::string &name) const {
356 if (find_json_value(m_data, name) == NULL) {
357 return false;
358 } else {
359 return true;
360 }
361 }
362
363 } // end of namespace pism