/* -*-c++-*- */ /* osgEarth - Geospatial SDK for OpenSceneGraph * Copyright 2020 Pelican Mapping * http://osgearth.org * * osgEarth is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see */ #pragma once #include #include #include #include #include #include #include #include namespace osgDB { class Options; } #define OE_CONFIG_MOVE_SEMATICS namespace osgEarth { using namespace osgEarth::Util; using ConfigSet = std::vector; class URI; /** * Config is a general-purpose container for serializable data. You store an object's members * to Config, and then translate the Config to a particular format (like XML or JSON). Likewise, * the object can de-serialize a Config back into member data. Config support the optional<> * template for optional values. */ class OSGEARTH_EXPORT Config { protected: std::string _key; std::string _defaultValue; std::string _referrer; std::string _externalRef; ConfigSet _children; bool _isLocation = false; bool _isNumber = false; public: Config() = default; Config(const std::string& key) : _key(key) { } template explicit Config(const std::string& key, const T& value) : _key(key) { setValue(value); } explicit Config(const std::string& key, const Config& value) : _key(key) { add(value); } #ifdef OE_CONFIG_MOVE_SEMATICS explicit Config(const std::string& key, Config&& value) : _key(key) { add(value); } Config(Config&& rhs) = default; Config& operator=(Config&& rhs) = default; #endif // Copy functions Config(const Config& rhs) = default; Config& operator=(const Config& rhs) = default; /** * Referrer is the context for resolving relative pathnames that occur in this object. * For example, if the value is a filename "file.txt" and the referrer is "C:/temp/a.earth", * then the full path of the file is "C:/temp/file.txt". * * Calling this sets a referrer on this object and its children. */ void setReferrer(const std::string& value); /** Access this object's "relative-to" location. */ const std::string& referrer() const { return _referrer; } /** Referrer associated with a key */ const std::string referrer(const std::string& key) const { return child(key).referrer(); } /** Sets whether this Config's value represents a location, i.e. a URI, filename, or other string that can be relocated to be relative to a different referrer. */ void setIsLocation(bool tf) { _isLocation = tf; } bool isLocation() const { return _isLocation; } /** Hint that this Config came from an externally referenced resource. */ const std::string& externalRef() const { return _externalRef; } void setExternalRef(const std::string& externalRef) { _externalRef = externalRef; } /** Populate this object from an XML input stream. */ bool fromXML(std::istream& in); //! Populate this object from a URI bool fromURI(const URI&); /** Encode this object as JSON. */ std::string toJSON(bool pretty = false) const; /** Populate this object from a JSON string. */ bool fromJSON(const std::string& json); static Config readJSON(const std::string& json); /** True if this object contains no data. */ bool empty() const { return _key.empty() && _defaultValue.empty() && _children.empty(); } /** True is this object is a simple key/value pair with no children. */ bool isSimple() const { return !_key.empty() && !_defaultValue.empty() && _children.empty(); } /** The key value for this object */ std::string& key() { return _key; } const std::string& key() const { return _key; } /** The value corresponding to the key */ const std::string& value() const { return _defaultValue; } /** Main setValue method - see specializations inline below */ template void setValue(const T& value) { _defaultValue = Stringify() << value; } /** Child objects. */ ConfigSet& children() { return _children; } const ConfigSet& children() const { return _children; } /** A collection of all the children of this object with a particular key */ const ConfigSet children(const std::string& key) const { ConfigSet r; for (ConfigSet::const_iterator i = _children.begin(); i != _children.end(); i++) { if (i->key() == key) r.push_back(*i); } return r; } /** Whether this object has a child with a given key */ bool hasChild(const std::string& key) const { for (ConfigSet::const_iterator i = _children.begin(); i != _children.end(); i++) if (i->key() == key) return true; return false; } /** Removes all children with the given key */ void remove(const std::string& key) { for (ConfigSet::iterator i = _children.begin(); i != _children.end(); ) { if (i->key() == key) i = _children.erase(i); else ++i; } } /** First child with the given key */ const Config& child(const std::string& key) const; /** Pointer to the first child with the given key, or NULL if none exist */ const Config* child_ptr(const std::string& key) const; /** Mutable pointer to the first child with the given key, or NULL if none exist */ Config* mutable_child(const std::string& key); /** Merge the contents of another Config object into this object.. danger, read the code before you use this */ void merge(const Config& rhs); /** Locate (recursively) the first descendant object with this key, optionally checking the current object as well */ Config* find(const std::string& key, bool checkThis = true); const Config* find(const std::string& key, bool checkThis = true) const; /** Add a value as a child */ template Config& add(const std::string& key, const T& value) { _children.push_back(Config(key, value)); _children.back().setReferrer(_referrer); return _children.back(); } //! Set a config as a child Config& add(const Config& conf) { _children.push_back(conf); _children.back().setReferrer(_referrer); return _children.back(); } /** Add a config as a child, assigning it a key */ Config& add(const std::string& key, const Config& conf) { Config& result = add(conf); result.key() = key; return result; } //! Add a config named key Config& add(const std::string& key) { return add(Config(key)); } /** Add a set of config objects as children. */ void add(const ConfigSet& set) { for (ConfigSet::const_iterator i = set.begin(); i != set.end(); i++) add(*i); } #ifdef OE_CONFIG_MOVE_SEMATICS //! Move a config as a child Config& add(Config&& conf) { _children.push_back(std::move(conf)); _children.back().setReferrer(_referrer); conf = {}; return _children.back(); } Config& add(const std::string& key, Config&& conf) { Config& result = add(std::move(conf)); result.key() = key; conf = {}; return result; } Config& set(Config&& conf) { remove(conf.key()); return add(std::move(conf)); } #endif /** Adds or replaces an optional value as a child, but only if it is set */ template void set(const std::string& key, const optional& opt) { remove(key); if (opt.isSet()) { set(Config(key, opt.get())); } } template void set(const std::string& key, const osg::ref_ptr& obj) { remove(key); if (obj.valid()) { Config conf = obj->getConfig(); conf.key() = key; set(std::move(conf)); } } // If target is set to targetValue, set key to val. template void set(const std::string& key, const std::string& val, const optional& target, const Y& targetValue) { if (target.isSetTo(targetValue)) { remove(key); set(key, val); } } /** Adds or replaces a config as a child. */ Config& set(const Config& conf) { remove(conf.key()); return add(conf); } //! Adds or replaces a config as a child if it's not empty. template void set_with_function(const std::string& key, CALLABLE&& f) { Config conf(key); f(conf); if (!conf.empty()) set(std::move(conf)); } /** Sets a key value pair child */ template void set(const std::string& key, const T& value) { set(Config(key, value)); } /** Whether this object has the key OR has a child with the key */ bool hasValue(const std::string& key) const { return !value(key).empty(); } /** The value of this object (if the key matches) or a matching child object */ const std::string value(const std::string& key) const { std::string r = trim(child(key).value()); if (r.empty() && _key == key) r = _defaultValue; return r; } /** Default value transformed to another type */ template T valueAs(const T& fallback) const { return osgEarth::Util::as(_defaultValue, fallback); } /** Value cast to a particular primitive type (with fallback in case casting fails) */ template T value(const std::string& key, T fallback) const { std::string r; if (hasChild(key)) r = child(key).value(); return osgEarth::Util::as(r, fallback); } /** Populates the output value iff the Config exists. */ template bool get(const std::string& key, optional& output) const { std::string r; if (hasChild(key)) r = child(key).value(); if (!r.empty()) { output = osgEarth::Util::as(r, output.defaultValue()); return true; } else return false; } /** Populates the output referenced value iff the Config exists. */ template bool get(const std::string& key, osg::ref_ptr& output) const { if (hasChild(key)) { output = new T(child(key)); return true; } else return false; } /** Populates the output enumerable pair iff the Config exists. */ template bool get(const std::string& key, const std::string& val, optional& target, const Y& targetValue) const { if (hasValue(key) && value(key) == val) { target = targetValue; return true; } else return false; } /** Populates the output enumerable pair iff the Config exists. */ template bool get(const std::string& key, const std::string& val, X& target, const Y& targetValue) const { if (hasValue(key) && value(key) == val) { target = targetValue; return true; } return false; } /** Populates the ouptut value iff the Config exists. */ template bool get(const std::string& key, T& output) const { if (hasValue(key)) { output = value(key, output); return true; } return false; } // remove everything from (this) that also appears in rhs (diff) Config operator - (const Config& rhs) const; //! Whether the encoded value is actual a number bool isNumber() const { return _isNumber; } }; // SPECIALIZATION - Config template<> inline void Config::set(const std::string& key, const Config& conf) { remove(key); Config& result = add(conf); result.key() = key; } template<> inline void Config::set(const std::string& key, const optional& opt) { remove(key); if (opt.isSet()) { Config& result = add(opt.value()); result.key() = key; } } template<> inline bool Config::get(const std::string& key, optional& output) const { if (hasChild(key)) { output = child(key); return true; } else return false; } // SPECIALIZATIONS - setValue template<> inline void Config::setValue(const std::string& value) { _defaultValue = value; _isNumber = false; } template<> inline void Config::setValue(const bool& value) { _defaultValue = value==true? "true" : "false"; _isNumber = false; } template<> inline void Config::setValue(const short& value) { _defaultValue = std::to_string(value); _isNumber = true; } template<> inline void Config::setValue(const unsigned short& value) { _defaultValue = std::to_string(value); _isNumber = true; } template<> inline void Config::setValue(const int& value) { _defaultValue = std::to_string(value); _isNumber = true; } template<> inline void Config::setValue(const unsigned int& value) { _defaultValue = std::to_string(value); _isNumber = true; } template<> inline void Config::setValue(const long& value) { _defaultValue = std::to_string(value); _isNumber = true; } template<> inline void Config::setValue(const unsigned long& value) { _defaultValue = std::to_string(value); _isNumber = true; } template<> inline void Config::setValue(const float& value) { _defaultValue = Stringify() << std::setprecision(8) << value; _isNumber = true; } template<> inline void Config::setValue(const double& value) { _defaultValue = Stringify() << std::setprecision(16) << value; _isNumber = true; } // Specializations (OSG types) template<> inline void Config::set(const std::string& key, const optional& opt) { remove(key); if (opt.isSet()) { set(key, Stringify() << std::setprecision(8) << opt->x() << "," << opt->y()); } } template<> inline bool Config::get(const std::string& key, optional& output) const { if (hasChild(key)) { output.mutable_value().x() = as(getToken(value(key), 0), 0.0f); output.mutable_value().y() = as(getToken(value(key), 1), 0.0f); return true; } else return false; } template<> inline void Config::set(const std::string& key, const optional& opt) { remove(key); if (opt.isSet()) { set(key, Stringify() << std::setprecision(16) << opt->x() << "," << opt->y()); } } template<> inline bool Config::get(const std::string& key, optional& output) const { if (hasChild(key)) { output.mutable_value().x() = as(getToken(value(key), 0), 0.0); output.mutable_value().y() = as(getToken(value(key), 1), 0.0); return true; } else return false; } template<> inline void Config::set(const std::string& key, const optional& opt) { remove(key); if (opt.isSet()) { set(key, Stringify() << std::setprecision(8) << opt->x() << "," << opt->y() << "," << opt->z()); } } template<> inline bool Config::get(const std::string& key, optional& output) const { if (hasChild(key)) { output.mutable_value().x() = as(getToken(value(key), 0), 0.0f); output.mutable_value().y() = as(getToken(value(key), 1), 0.0f); output.mutable_value().z() = as(getToken(value(key), 2), 0.0f); return true; } else return false; } template<> inline void Config::set(const std::string& key, const optional& opt) { remove(key); if (opt.isSet()) { set(key, Stringify() << std::setprecision(16) << opt->x() << "," << opt->y() << "," << opt->z()); } } template<> inline bool Config::get(const std::string& key, optional& output) const { if (hasChild(key)) { output.mutable_value().x() = as(getToken(value(key), 0), 0.0); output.mutable_value().y() = as(getToken(value(key), 1), 0.0); output.mutable_value().z() = as(getToken(value(key), 2), 0.0); return true; } else return false; } template<> inline void Config::set(const std::string& key, const std::vector& input) { remove(key); if (!input.empty()) { std::ostringstream buf; for(size_t i=0; i 0) buf << ','; bool quote = input[i].find(',') != std::string::npos; if (quote) buf << "\""; buf << input[i]; if (quote) buf << "\""; } set(key, buf.str()); } } template<> inline bool Config::get(const std::string& key, std::vector& output) const { if (hasChild(key)) { output.clear(); std::vector tokens; StringTokenizer(value(key), output, ",", "\"", true, true); return true; } else return false; } // Use this macro to "activate" any object with a getConfig/ctor(const Config&) pair // and make it usable with Config::set/get/add. // NOTE: You must only use this macro in the global namespace! #define OSGEARTH_SPECIALIZE_CONFIG(TYPE) \ namespace osgEarth { \ template<> inline \ void Config::set(const std::string& key, const TYPE& obj) { \ set( key, obj.getConfig() ); \ } \ template<> inline \ void Config::set(const std::string& key, const optional& opt) { \ if ( opt.isSet() ) \ set(key, opt.get()); \ } \ template<> inline \ bool Config::get(const std::string& key, TYPE& opt) const { \ if ( hasChild(key) ) { \ opt = TYPE(child(key)); \ return true; \ } \ else return false; \ } \ template<> inline \ bool Config::get(const std::string& key, optional& opt) const { \ if ( hasChild(key) ) { \ opt = TYPE(child(key)); \ return true; \ } \ else return false; \ } \ template<> inline Config& Config::add(const std::string& key, const TYPE& value) { \ Config conf = value.getConfig(); \ conf.key() = key; \ return add(std::move(conf)); \ } \ } // namespace osgEarth //-------------------------------------------------------------------- /** * Base class for all serializable options classes. */ class ConfigOptions { public: ConfigOptions() = default; //! Copy constructor - it must call getConfig() so it can repopulate the internal _conf member. ConfigOptions(const ConfigOptions& rhs) : _conf(rhs.getConfig()) { } //! Copy constructor - simple Config copy. ConfigOptions(const Config& conf) : _conf(conf) { } const std::string& referrer() const { return _conf.referrer(); } ConfigOptions& operator = (const ConfigOptions& rhs) { if (this != &rhs) { _conf = rhs.getConfig(); mergeConfig(_conf); } return *this; } void merge(const ConfigOptions& rhs) { _conf.merge(rhs._conf); mergeConfig(rhs.getConfig()); } virtual Config getConfig() const { Config conf = _conf; conf.setReferrer(referrer()); return conf; } bool empty() const { return _conf.empty(); } protected: virtual void mergeConfig(const Config& conf) { } Config _conf; }; /** * Base configoptions class for driver options. * @deprecated - will be removed. */ class DriverConfigOptions : public ConfigOptions { public: DriverConfigOptions(const ConfigOptions& rhs = ConfigOptions()) : ConfigOptions(rhs) { fromConfig(_conf); } /** Gets or sets the name of the driver to load */ void setDriver(const std::string& value) { _driver = value; } const std::string& getDriver() const { return _driver; } public: virtual Config getConfig() const { Config conf = ConfigOptions::getConfig(); if (!_driver.empty()) conf.set("driver", _driver); return conf; } virtual void mergeConfig(const Config& conf) { ConfigOptions::mergeConfig(conf); fromConfig(conf); } public: void fromConfig(const Config& conf) { _driver = conf.value("driver"); if (_driver.empty() && conf.hasValue("type")) _driver = conf.value("type"); } private: std::string _name, _driver; }; } //! Macro to use when defining a COnfigOptions class #define META_ConfigOptions(LIBRARY, MYCLASS, SUPERCLASS) \ protected: \ virtual void mergeConfig(const Config& conf) { \ SUPERCLASS ::mergeConfig(conf); \ fromConfig(conf); \ } \ public: \ MYCLASS () : SUPERCLASS() { fromConfig(_conf); } \ MYCLASS (const ConfigOptions& opt) : SUPERCLASS(opt) { fromConfig(_conf); } //! optional property macro #define OE_OPTION_0_ARGS() #define OE_OPTION_1_ARGS() #define OE_OPTION_2_ARGS(TYPE, NAME) \ private: \ osgEarth::optional< TYPE > _ ## NAME ; \ mutable std::vector> _ ## NAME ## _listeners; \ public: \ osgEarth::optional< TYPE >& NAME () { return _ ## NAME; } \ const osgEarth::optional< TYPE >& NAME () const { return _ ## NAME; } \ void NAME ## Changed (std::function f) const { \ _ ## NAME ## _listeners.push_back(f); } \ void set_ ## NAME (const TYPE& value) { \ _ ## NAME = value; \ for(auto& notify : _ ## NAME ## _listeners) notify(value); } #define OE_OPTION_3_ARGS(TYPE, NAME, DEFAULT_VALUE) \ private: \ osgEarth::optional< TYPE > _ ## NAME {DEFAULT_VALUE}; \ mutable std::vector> _ ## NAME ## _listeners; \ public: \ osgEarth::optional< TYPE >& NAME () { return _ ## NAME; } \ const osgEarth::optional< TYPE >& NAME () const { return _ ## NAME; } \ void NAME ## Changed (std::function f) const { \ _ ## NAME ## _listeners.push_back(f); } \ void set_ ## NAME (const TYPE& value) { \ _ ## NAME = value; \ for(auto& notify : _ ## NAME ## _listeners) notify(value); } // https://stackoverflow.com/a/28074198 #define OE_OPTION_FUNC_CHOOSER(_f1, _f2, _f3, _f4, ...) _f4 #define OE_OPTION_FUNC_RECOMPOSER(ARGS) OE_OPTION_FUNC_CHOOSER ARGS #define OE_OPTION_CHOOSE_FROM_ARG_COUNT(...) OE_OPTION_FUNC_RECOMPOSER((__VA_ARGS__, OE_OPTION_3_ARGS, OE_OPTION_2_ARGS, OE_OPTION_1_ARGS, )) #define OE_OPTION_NO_ARG_EXPANDER() ,,OE_OPTION_0_ARGS #define OE_OPTION_MACRO_CHOOSER(...) OE_OPTION_CHOOSE_FROM_ARG_COUNT(OE_OPTION_NO_ARG_EXPANDER __VA_ARGS__ ()) #define OE_OPTION(...) OE_OPTION_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__) //! ref_ptr property macro #define OE_OPTION_REFPTR(TYPE, NAME) \ private: \ osg::ref_ptr< TYPE > _ ## NAME ; \ public: \ osg::ref_ptr< TYPE >& NAME () { return _ ## NAME; } \ const osg::ref_ptr< TYPE >& NAME () const { return _ ## NAME; } //! ref_ptr property macro #define OE_OPTION_SHAREDPTR(TYPE, NAME) \ private: \ std::shared_ptr< TYPE > _ ## NAME ; \ public: \ std::shared_ptr< TYPE >& NAME () { return _ ## NAME; } \ const std::shared_ptr< TYPE >& NAME () const { return _ ## NAME; } //! vector property macro #define OE_OPTION_VECTOR(TYPE, NAME) \ private: \ std::vector< TYPE > _ ## NAME ; \ public: \ std::vector< TYPE >& NAME () { return _ ## NAME; } \ const std::vector< TYPE >& NAME () const { return _ ## NAME; } #define OE_OPTION_IMPL(CLASS, TYPE, FUNC, OPTION) \ void CLASS ::set ## FUNC (const TYPE & value) { options(). set_ ## OPTION (value); }\ const TYPE & CLASS ::get ## FUNC () const { return options(). OPTION ().get(); } //! property macro #define OE_PROPERTY(TYPE, NAME, DEFVAL) \ private: \ TYPE _ ## NAME = DEFVAL; \ public: \ TYPE & NAME () { return _ ## NAME; } \ TYPE & NAME ## _mutable() { return _ ## NAME; } \ const TYPE & NAME () const { return _ ## NAME; } //! const property macro #define OE_PROPERTY_CONST(TYPE, NAME, DEFVAL) \ private: \ TYPE _ ## NAME = DEFVAL; \ protected: \ TYPE & NAME () { return _ ## NAME; } \ TYPE & NAME ## _mutable() { return _ ## NAME; } \ public: \ const TYPE & NAME () const { return _ ## NAME; } #define OE_OPTION_LESS(L,R) \ if (L().isSet() && !R().isSet()) return true; \ if (R().isSet() && !L().isSet()) return false; \ if (L().isSet() && R().isSet()) { \ if (L().get() < R().get()) return true; \ if (L().get() > R().get()) return false; }