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