/* Copyright 2013--2018 James E. McClure, Virginia Polytechnic & State University Copyright Equnior ASA This file is part of the Open Porous Media project (OPM). OPM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OPM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OPM. If not, see . */ #include "common/Database.h" #include "common/Utilities.h" #include #include #include #include #include #include /******************************************************************** * Constructors/destructor * ********************************************************************/ Database::Database() = default; Database::~Database() = default; Database::Database(const Database &rhs) : KeyData(rhs) { d_data.clear(); for (const auto &tmp : rhs.d_data) putData(tmp.first, tmp.second->clone()); } Database &Database::operator=(const Database &rhs) { if (this == &rhs) return *this; d_data.clear(); for (const auto &tmp : rhs.d_data) putData(tmp.first, tmp.second->clone()); return *this; } Database::Database(Database &&rhs) { std::swap(d_data, rhs.d_data); } Database &Database::operator=(Database &&rhs) { if (this != &rhs) std::swap(d_data, rhs.d_data); return *this; } /******************************************************************** * Clone the database * ********************************************************************/ std::shared_ptr Database::clone() const { return cloneDatabase(); } std::shared_ptr Database::cloneDatabase() const { auto db = std::make_shared(); for (const auto &tmp : d_data) db->putData(tmp.first, tmp.second->clone()); return db; } /******************************************************************** * Get the data object * ********************************************************************/ bool Database::keyExists(const std::string &key) const { return d_data.find(key) != d_data.end(); } std::shared_ptr Database::getData(const std::string &key) { auto it = d_data.find(key); if (it == d_data.end()) { char msg[1000]; sprintf(msg, "Variable %s was not found in database", key.c_str()); ERROR(msg); } return it->second; } std::shared_ptr Database::getData(const std::string &key) const { return const_cast(this)->getData(key); } bool Database::isDatabase(const std::string &key) const { auto ptr = getData(key); auto ptr2 = std::dynamic_pointer_cast(ptr); return ptr2 != nullptr; } std::shared_ptr Database::getDatabase(const std::string &key) { std::shared_ptr ptr = getData(key); std::shared_ptr ptr2 = std::dynamic_pointer_cast(ptr); if (ptr2 == nullptr) { char msg[1000]; sprintf(msg, "Variable %s is not a database", key.c_str()); ERROR(msg); } return ptr2; } std::shared_ptr Database::getDatabase(const std::string &key) const { return const_cast(this)->getDatabase(key); } std::vector Database::getAllKeys() const { std::vector keys; keys.reserve(d_data.size()); for (const auto &it : d_data) keys.push_back(it.first); return keys; } void Database::putDatabase(const std::string &key, std::shared_ptr db) { d_data[key] = std::move(db); } void Database::putData(const std::string &key, std::shared_ptr data) { d_data[key] = std::move(data); } /******************************************************************** * Is the data of the given type * ********************************************************************/ template <> bool Database::isType(const std::string &key) const { auto type = getData(key)->type(); return type == "double"; } template <> bool Database::isType(const std::string &key) const { auto type = getData(key)->type(); return type == "double"; } template <> bool Database::isType(const std::string &key) const { bool pass = true; auto type = getData(key)->type(); if (type == "double") { auto data = getVector(key); for (auto tmp : data) pass = pass && static_cast(static_cast(tmp)) == tmp; } else { pass = false; } return pass; } template <> bool Database::isType(const std::string &key) const { auto type = getData(key)->type(); return type == "string"; } template <> bool Database::isType(const std::string &key) const { auto type = getData(key)->type(); return type == "bool"; } /******************************************************************** * Get a vector * ********************************************************************/ template <> std::vector Database::getVector(const std::string &key, const Units &) const { std::shared_ptr ptr = getData(key); if (std::dynamic_pointer_cast(ptr)) return std::vector(); const auto *ptr2 = dynamic_cast(ptr.get()); if (ptr2 == nullptr) { ERROR("Key '" + key + "' is not a string"); } return ptr2->d_data; } template <> std::vector Database::getVector(const std::string &key, const Units &) const { std::shared_ptr ptr = getData(key); if (std::dynamic_pointer_cast(ptr)) return std::vector(); const auto *ptr2 = dynamic_cast(ptr.get()); if (ptr2 == nullptr) { ERROR("Key '" + key + "' is not a bool"); } return ptr2->d_data; } template std::vector Database::getVector(const std::string &key, const Units &unit) const { std::shared_ptr ptr = getData(key); if (std::dynamic_pointer_cast(ptr)) return std::vector(); std::vector data; if (std::dynamic_pointer_cast(ptr)) { const auto *ptr2 = dynamic_cast(ptr.get()); const std::vector &data2 = ptr2->d_data; double factor = 1; if (!unit.isNull()) { INSIST(!ptr2->d_unit.isNull(), "Field " + key + " must have units"); factor = ptr2->d_unit.convert(unit); INSIST(factor != 0, "Unit conversion failed"); } data.resize(data2.size()); for (size_t i = 0; i < data2.size(); i++) data[i] = static_cast(factor * data2[i]); } else if (std::dynamic_pointer_cast(ptr)) { ERROR("Converting std::string to another type"); } else if (std::dynamic_pointer_cast(ptr)) { ERROR("Converting std::bool to another type"); } else { ERROR("Unable to convert data format"); } return data; } /******************************************************************** * Put a vector * ********************************************************************/ template <> void Database::putVector(const std::string &key, const std::vector &data, const Units &) { std::shared_ptr ptr(new KeyDataString()); ptr->d_data = data; d_data[key] = ptr; } template <> void Database::putVector(const std::string &key, const std::vector &data, const Units &) { std::shared_ptr ptr(new KeyDataBool()); ptr->d_data = data; d_data[key] = ptr; } template void Database::putVector(const std::string &key, const std::vector &data, const Units &unit) { std::shared_ptr ptr(new KeyDataDouble()); ptr->d_unit = unit; ptr->d_data.resize(data.size()); for (size_t i = 0; i < data.size(); i++) ptr->d_data[i] = static_cast(data[i]); d_data[key] = ptr; } /******************************************************************** * Print the database * ********************************************************************/ void Database::print(std::ostream &os, const std::string &indent) const { for (const auto &it : d_data) { os << indent << it.first; if (dynamic_cast(it.second.get())) { const auto *db = dynamic_cast(it.second.get()); os << " {\n"; db->print(os, indent + " "); os << indent << "}\n"; } else { os << " = "; it.second->print(os, ""); } } } std::string Database::print(const std::string &indent) const { std::stringstream ss; print(ss, indent); return ss.str(); } /******************************************************************** * Read input database file * ********************************************************************/ Database::Database(const std::string &filename) { // Read the input file into memory FILE *fid = fopen(filename.c_str(), "rb"); if (fid == nullptr) ERROR("Error opening file " + filename); fseek(fid, 0, SEEK_END); size_t bytes = ftell(fid); rewind(fid); auto *buffer = new char[bytes + 4]; size_t result = fread(buffer, 1, bytes, fid); fclose(fid); if (result != bytes) ERROR("Error reading file " + filename); buffer[bytes + 0] = '\n'; buffer[bytes + 1] = '}'; buffer[bytes + 2] = '\n'; buffer[bytes + 3] = 0; // Create the database entries loadDatabase(buffer, *this); // Free temporary memory delete[] buffer; } std::shared_ptr Database::createFromString(const std::string &data) { std::shared_ptr db(new Database()); auto *buffer = new char[data.size() + 4]; memcpy(buffer, data.data(), data.size()); buffer[data.size() + 0] = '\n'; buffer[data.size() + 1] = '}'; buffer[data.size() + 2] = '\n'; buffer[data.size() + 3] = 0; loadDatabase(buffer, *db); delete[] buffer; return db; } enum class token_type { newline, line_comment, block_start, block_stop, quote, equal, bracket, end_bracket, end }; inline size_t length(token_type type) { size_t len = 0; if (type == token_type::newline || type == token_type::quote || type == token_type::equal || type == token_type::bracket || type == token_type::end_bracket || type == token_type::end) { len = 1; } else if (type == token_type::line_comment || type == token_type::block_start || type == token_type::block_stop) { len = 2; } return len; } inline std::tuple find_next_token(const char *buffer) { size_t i = 0; while (true) { if (buffer[i] == '\n' || buffer[i] == '\r') { return std::pair(i + 1, token_type::newline); } else if (buffer[i] == 0) { return std::pair(i + 1, token_type::end); } else if (buffer[i] == '"') { return std::pair(i + 1, token_type::quote); } else if (buffer[i] == '=') { return std::pair(i + 1, token_type::equal); } else if (buffer[i] == '{') { return std::pair(i + 1, token_type::bracket); } else if (buffer[i] == '}') { return std::pair(i + 1, token_type::end_bracket); } else if (buffer[i] == '/') { if (buffer[i + 1] == '/') { return std::pair(i + 2, token_type::line_comment); } else if (buffer[i + 1] == '*') { return std::pair(i + 2, token_type::block_start); } } else if (buffer[i] == '*') { if (buffer[i + 1] == '/') return std::pair(i + 2, token_type::block_stop); } i++; } return std::pair(0, token_type::end); } inline std::string deblank(const std::string &str) { size_t i1 = 0xFFFFFFF, i2 = 0; for (size_t i = 0; i < str.size(); i++) { if (str[i] != ' ') { i1 = std::min(i1, i); i2 = std::max(i2, i); } } return i1 <= i2 ? str.substr(i1, i2 - i1 + 1) : std::string(); } size_t skip_comment(const char *buffer) { auto tmp = find_next_token(buffer); const token_type end_comment = (std::get<1>(tmp) == token_type::line_comment) ? token_type::newline : token_type::block_stop; size_t pos = 0; while (std::get<1>(tmp) != end_comment) { if (std::get<1>(tmp) == token_type::end) ERROR("Encountered end of file before block comment end"); pos += std::get<0>(tmp); tmp = find_next_token(&buffer[pos]); } pos += std::get<0>(tmp); return pos; } inline std::string lower(const std::string &str) { std::string tmp(str); std::transform(tmp.begin(), tmp.end(), tmp.begin(), ::tolower); return tmp; } static std::tuple> read_value(const char *buffer, const std::string &key) { // Get the value as a std::string size_t pos = 0; token_type type = token_type::end; std::tie(pos, type) = find_next_token(&buffer[pos]); size_t len = pos - length(type); while (type != token_type::newline) { if (type == token_type::quote) { size_t i = 0; std::tie(i, type) = find_next_token(&buffer[pos]); pos += i; while (type != token_type::quote) { ASSERT(type != token_type::end); std::tie(i, type) = find_next_token(&buffer[pos]); pos += i; } } else if (type == token_type::line_comment || type == token_type::block_start) { len = pos - length(type); pos += skip_comment(&buffer[pos - length(type)]) - length(type); break; } size_t i = 0; std::tie(i, type) = find_next_token(&buffer[pos]); pos += i; len = pos - length(type); } const std::string value = deblank(std::string(buffer, len)); // Split the value to an array of values std::vector values; size_t i0 = 0, i = 0, count = 0; for (; i < value.size(); i++) { if (value[i] == '"') { count++; } else if (value[i] == ',' && count % 2 == 0) { values.push_back(deblank(value.substr(i0, i - i0))); i0 = i + 1; } } values.push_back(deblank(value.substr(i0))); // Convert the string value to the database value std::shared_ptr data; if (value.empty()) { data.reset(new EmptyKeyData()); } else if (value.find('"') != std::string::npos) { auto *data2 = new KeyDataString(); data.reset(data2); data2->d_data.resize(values.size()); for (size_t i = 0; i < values.size(); i++) { ASSERT(values[i].size() >= 2); ASSERT(values[i][0] == '"' && values[i][values[i].size() - 1] == '"'); data2->d_data[i] = values[i].substr(1, values[i].size() - 2); } } else if (lower(value) == "true" || lower(value) == "false") { auto *data2 = new KeyDataBool(); data.reset(data2); data2->d_data.resize(values.size()); for (size_t i = 0; i < values.size(); i++) { ASSERT(values[i].size() >= 2); if (lower(values[i]) != "true" && lower(values[i]) != "false") ERROR("Error converting " + key + " to logical array"); data2->d_data[i] = lower(values[i]) == "true"; } } else { // if ( value.find('.')!=std::string::npos || value.find('e')!=std::string::npos ) { auto *data2 = new KeyDataDouble(); data.reset(data2); data2->d_data.resize(values.size(), 0); for (size_t i = 0; i < values.size(); i++) { Units unit; std::tie(data2->d_data[i], unit) = KeyDataDouble::read(values[i]); if (!unit.isNull()) data2->d_unit = unit; } //} else { // ERROR("Unable to determine data type: "+value); } return std::tuple>(pos, data); } size_t Database::loadDatabase(const char *buffer, Database &db) { size_t pos = 0; while (true) { size_t i; token_type type; std::tie(i, type) = find_next_token(&buffer[pos]); const std::string key = deblank( std::string(&buffer[pos], std::max(i - length(type), 1) - 1)); if (type == token_type::line_comment || type == token_type::block_start) { // Comment INSIST(key.empty(), "Key should be empty: " + key); pos += skip_comment(&buffer[pos]); } else if (type == token_type::newline) { INSIST(key.empty(), "Key should be empty: " + key); pos += i; } else if (type == token_type::equal) { // Reading key/value pair ASSERT(!key.empty()); pos += i; std::shared_ptr data; std::tie(i, data) = read_value(&buffer[pos], key); ASSERT(data.get() != nullptr); db.d_data[key] = data; pos += i; } else if (type == token_type::bracket) { // Read database ASSERT(!key.empty()); pos += i; std::shared_ptr database(new Database()); pos += loadDatabase(&buffer[pos], *database); db.d_data[key] = database; } else if (type == token_type::end_bracket) { // Finished with the database pos += i; break; } else { ERROR("Error loading data"); } } return pos; } /******************************************************************** * Data type helper functions * ********************************************************************/ void KeyDataDouble::print(std::ostream &os, const std::string &indent) const { os << indent; for (size_t i = 0; i < d_data.size(); i++) { if (i > 0) os << ", "; if (d_data[i] != d_data[i]) { os << "nan"; } else if (d_data[i] == std::numeric_limits::infinity()) { os << "inf"; } else if (d_data[i] == -std::numeric_limits::infinity()) { os << "-inf"; } else { os << std::setprecision(12) << d_data[i]; } } if (!d_unit.isNull()) os << " " << d_unit.str(); os << std::endl; } std::tuple KeyDataDouble::read(const std::string &str) { std::string tmp = deblank(str); size_t index = tmp.find(" "); if (index != std::string::npos) { return std::make_tuple(readValue(tmp.substr(0, index)), Units(tmp.substr(index + 1))); } else { return std::make_tuple(readValue(tmp), Units()); } } double KeyDataDouble::readValue(const std::string &str) { const std::string tmp = lower(str); double data = 0; if (tmp == "inf" || tmp == "infinity") { data = std::numeric_limits::infinity(); } else if (tmp == "-inf" || tmp == "-infinity") { data = -std::numeric_limits::infinity(); } else if (tmp == "nan") { data = std::numeric_limits::quiet_NaN(); } else if (tmp.find('/') != std::string::npos) { ERROR("Error reading value"); } else { char *pos = nullptr; data = strtod(tmp.c_str(), &pos); if (static_cast(pos - tmp.c_str()) == tmp.size() + 1) ERROR("Error reading value"); } return data; } /******************************************************************** * Instantiations * ********************************************************************/ template std::vector Database::getVector(const std::string &, const Units &) const; template std::vector Database::getVector(const std::string &, const Units &) const; template std::vector Database::getVector(const std::string &, const Units &) const; template std::vector Database::getVector(const std::string &, const Units &) const; template std::vector Database::getVector(const std::string &, const Units &) const; template void Database::putVector(const std::string &, const std::vector &, const Units &); template void Database::putVector(const std::string &, const std::vector &, const Units &); template void Database::putVector(const std::string &, const std::vector &, const Units &); template void Database::putVector(const std::string &, const std::vector &, const Units &); template void Database::putVector(const std::string &, const std::vector &, const Units &); template bool Database::isType(const std::string &) const; template bool Database::isType(const std::string &) const; template bool Database::isType(const std::string &) const; template bool Database::isType(const std::string &) const;