From: Reinhard Pfau Date: Tue, 26 Aug 2008 10:50:09 +0000 (+0000) Subject: libi2ncommon: (reinhard) added global_config module (as separate lib and package... X-Git-Tag: v2.6~154 X-Git-Url: http://developer.intra2net.com/git/?a=commitdiff_plain;h=23d53eb19bbda24d8ac8af491ee98e3b3828f858;p=libi2ncommon libi2ncommon: (reinhard) added global_config module (as separate lib and package: libi2ncommon_config) --- diff --git a/Makefile.am b/Makefile.am index 3dcc619..b4a6b0a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,11 +2,11 @@ # have all needed files, that a GNU package needs AUTOMAKE_OPTIONS = foreign 1.4 -SUBDIRS = src test +SUBDIRS = src configlib test # Install the pkg-config file: pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = libi2ncommon.pc +pkgconfig_DATA = libi2ncommon.pc libi2ncommon_config.pc if AUTOCHECK all: config.h diff --git a/configlib/Makefile.am b/configlib/Makefile.am new file mode 100644 index 0000000..3889c49 --- /dev/null +++ b/configlib/Makefile.am @@ -0,0 +1,18 @@ +# set the include path found by configure +INCLUDES = -I$(top_srcdir)/src $(all_includes) @LIBGETTEXT_CFLAGS@ \ + @LIBICONV_CFLAGS@ + +METASOURCES = AUTO + +lib_LTLIBRARIES = libi2ncommon_config.la + +include_HEADERS = i2n_global_config.hpp + +# Note: If you specify a:b:c as the version in the next line, +# the library that is made has version (a-c).c.b. In this +# example, the version is 2.1.2. (3:2:1) + +libi2ncommon_config_la_LDFLAGS = -version-info @LIBI2NCOMMON_LIB_VERSION@ + +libi2ncommon_config_la_LIBADD = +libi2ncommon_config_la_SOURCES = i2n_global_config.cpp diff --git a/configlib/i2n_global_config.cpp b/configlib/i2n_global_config.cpp new file mode 100644 index 0000000..5c4c1e8 --- /dev/null +++ b/configlib/i2n_global_config.cpp @@ -0,0 +1,437 @@ +/** @file + * + * @author Reinhard Pfau \ + * + * @copyright © Copyright 2008 by Intra2net AG + * @license commercial + * @contact info@intra2net.com + */ + +#include "i2n_global_config.hpp" + + +#include +#include + +#include +#include +#include +#include +#include +#include + +//#define NOISEDEBUG +#undef NOISEDEBUG + +#ifdef NOISEDEBUG +#include +#include +#define DOUT(msg) std::cout << msg << std::endl +#else +#define DOUT(msg) do {} while (0) +#endif + + +namespace I2n +{ + +namespace Config +{ + + +namespace Detail +{ + + +} // eo namespace Detail + + + +namespace +{ + + +Logger::PartLogger& module_logger() +{ + static Logger::PartLogger _logger(HERE); + return _logger; +} // eo module_logger() + + + +std::string config_file_name; + +ConfigData config_data; + + +/* +** +*/ + + +struct HideOut +: public Detail::Thing +{ + HideOut( + const std::string& section, + const std::string& key, + const Detail::FeedFunction& feed, + const Detail::FeedFunction2& feed2); + + virtual ~HideOut(); + + Detail::FeedFunction m_feed; + Detail::FeedFunction2 m_feed2; + std::string m_section; + std::string m_key; +}; // eo struct HideOut + +typedef boost::shared_ptr< HideOut > HideOutPtr; + + +class HideOutMap +: public GroupedKeyValueData< + std::string, + std::string, + HideOut* +> +{ + public: + HideOutMap(); + virtual ~HideOutMap(); + + public: + + static int Instances; +}; // eo class HideOutMap + +int HideOutMap::Instances= 0; + +HideOutMap::HideOutMap() +{ + DOUT("HideOutMap::HideOutMap()"); + ++Instances; +} // eo HideOutMap::HideOutMap() + + +HideOutMap::~HideOutMap() +{ + DOUT("HideOutMap::~HideOutMap()"); + --Instances; +} // eo HideOutMap::~HideOutMap() + + +HideOutMap& hideout_map() +{ + static HideOutMap the_hideout_map; + DOUT("hideout_map()"); + return the_hideout_map; +} // eo hideout_map() + + +// implementation of HideOut: + + +HideOut::HideOut( + const std::string& section, + const std::string& key, + const Detail::FeedFunction& feed, + const Detail::FeedFunction2& feed2) +: m_section(section) +, m_key(key) +, m_feed(feed) +, m_feed2(feed2) +{ + hideout_map()[section][key]+= this; +} // eo HideOut::HideOut(const FeedFunction&) + + + +HideOut::~HideOut() +{ + if (HideOutMap::Instances) + { + hideout_map()[m_section].removeValue(m_key,this); + } +} // eo HideOut::~HideOut() + + + +/* +** +*/ + + +/** + * @brief distributes (new) values to the active config variables. + * @param old_config_data reference to the old config data (for determining the deltas). + * @return @a true if new values could be converted without an error. + */ +bool feedEm( ConfigData& old_config_data ) +{ + SCOPETRACKER(); + bool result= true; + HideOutMap::KeyListType section_list; + hideout_map().getKeys(section_list); + for(HideOutMap::KeyListType::const_iterator section_it = section_list.begin(); + section_it != section_list.end(); + ++section_it) + { + std::string section( *section_it ); + DOUT("process section \"" << section << "\""); + HideOutMap::GroupDataType::KeyListType key_list; + hideout_map()[section].getKeys(key_list); + for(HideOutMap::GroupDataType::KeyListType::const_iterator key_it= key_list.begin(); + key_it != key_list.end(); + ++key_it) + { + std::string key( *key_it ); + DOUT("process key \"" << key << "\""); + bool has_new_data= config_data.hasKey(section, key); + bool has_old_data= old_config_data.hasKey(section, key); + if (not has_new_data and not has_old_data) + { + // we don't have data and we didn't had data... well, nothing to do: + DOUT("no data"); + continue; + } + if (has_new_data) + { + DOUT("has (new) data"); + StringList values; + config_data[section].getValues(key, values); + std::string value( config_data[section][key] ); + if (has_old_data) + { + StringList old_values; + old_config_data[section].getValues(key, old_values); + if (values == old_values) + { + // value didn't change, so we can skip this case. + DOUT("data is same as before"); + continue; + } + } + // at this point we need to publish (new or changed) value: + HideOutMap::GroupDataType::ValueListType func_list; + hideout_map()[section].getValues(key, func_list); + for(HideOutMap::GroupDataType::ValueListType::iterator func_it= func_list.begin(); + func_it != func_list.end(); + ++func_it) + { + DOUT(" feed iteration"); + if (not (*func_it)) //paranoia + { + continue; + } + if ( (*func_it)->m_feed) + { + if ( not (*func_it)->m_feed( value ) ) + { + module_logger().error() << "unable to publish [" <
m_feed2) + { + if ( not (*func_it)->m_feed2( values ) ) + { + module_logger().error() << "unable to publish [" <
+ + +/************************************************************************\ +\************************************************************************/ + + +namespace Detail +{ + + +FlowerShop seymour( + const std::string& section, + const std::string& key, + const FeedFunction& feed) +{ + DOUT("seymor(\"" << section << "\", \"" << key << "\",FeedFunction)"); + FlowerShop result( new HideOut( section, key, feed, 0 ) ); + if (not config_data.empty()) + { + if (config_data.hasKey(section, key)) + { + feed( config_data[section][key] ); + } + } + return result; +} // eo seymour(const std::string&,const std::string&,const FeedFunction&) + + +FlowerShop seymour( + const std::string& section, + const std::string& key, + const FeedFunction2& feed) +{ + DOUT("seymor(\"" << section << "\", \"" << key << "\",FeedFunction2)"); + FlowerShop result( new HideOut( section, key, 0, feed ) ); + if (not config_data.empty()) + { + if (config_data.hasKey(section, key)) + { + StringList values; + config_data[section].getValues(key, values); + feed( values ); + } + } + return result; +} // eo seymour(const std::string&,const std::string&,const FeedFunction2&) + + +FlowerShop seymour(FlowerShop shop, const FeedFunction& feed) +{ + if (!shop) + { + // no shop; no coordinates; no useful stuff to do... + return shop; + } + HideOutPtr ptr = boost::shared_dynamic_cast< HideOut >(shop); + if (!ptr) + { + // again, no coordinates... + return ptr; + } + FlowerShop result( new HideOut( ptr->m_section, ptr->m_key, feed, 0 ) ); + if (not config_data.empty()) + { + if (config_data.hasKey(ptr->m_section, ptr->m_key)) + { + feed( config_data[ptr->m_section][ptr->m_key] ); + } + } + return result; +} // eo seymour(FlowerShop,const FeedFunction&) + + +FlowerShop seymour(FlowerShop shop, const FeedFunction2& feed) +{ + if (!shop) + { + // no shop; no coordinates; no useful stuff to do... + return shop; + } + HideOutPtr ptr = boost::shared_dynamic_cast< HideOut >(shop); + if (!ptr) + { + // again, no coordinates... + return ptr; + } + FlowerShop result( new HideOut( ptr->m_section, ptr->m_key, 0, feed ) ); + if (not config_data.empty()) + { + if (config_data.hasKey(ptr->m_section, ptr->m_key)) + { + StringList values; + config_data[ptr->m_section].getValues(ptr->m_key, values); + feed( values ); + } + } + return result; +} // eo seymour(FlowerShop,const FeedFunction&) + + +} // eo namespace Detail + + + +/** + * @brief reloads the configuration from the last (known) config file. + * @return @a true if config was succesfully reloaded. + */ +bool reload() +{ + ConfigData old_config_data; + ConfigData new_config_data; + if (config_file_name.empty()) + { + return false; + } + bool loaded= load_ini_config_file(config_file_name, new_config_data); + bool publish_successful= true; + if (loaded) + { + module_logger().debug() << "new_config_data.empty(): " << new_config_data.empty(); + // save old data + old_config_data.swap( config_data ); + // and install the new data + new_config_data.swap( config_data ); + + // react on the config: publish the values: + publish_successful = feedEm( old_config_data ); + } + return loaded and publish_successful; +} // reload() + + + + +/** + * @brief sets the path of the config file and loads it. + * @param path the path to the config file. + * @return @a true iff the config could be loaded and all values pushed into their config vars. + */ +bool set_config_file(const std::string& path) +{ + if (not config_file_name.empty()) + { + // WTF?!... + // a new config file.. well... why not? + } + Stat stat(path); + if (not stat or not stat.is_regular_file()) + { + return false; + } + config_file_name= path; + return reload(); +} // eo set_config_file(const std::string&) + + +/** + * @brief returns the current config file path. + * @return the current path. + */ +std::string get_config_file() +{ + return config_file_name; +} // eo get_config_file() + + +} // eo namespace config + +} // eo namespace I2n diff --git a/configlib/i2n_global_config.hpp b/configlib/i2n_global_config.hpp new file mode 100644 index 0000000..82a03ac --- /dev/null +++ b/configlib/i2n_global_config.hpp @@ -0,0 +1,454 @@ +/** @file + * @brief provides config file reading and automatic propagation of the values. + * + * @author Reinhard Pfau \ + * + * @copyright © Copyright 2008 by Intra2net AG + * @license commercial + * @contact info@intra2net.com + * + * Basic idea of this module is that one need only to declare its config vars of + * the type Config::Var\< T \> and pass the desired section and key from the + * (INI style) config file where the value for this var should be searched. + * The string value from the config file is converted to the desired type using a + * converter class (a default is provided and uses a conversion based on string_to template + * from libi2ncommon). + * + * Populating the var is automagically done when the config is read or reread or when + * the variable is defined. + * + * + * @todo review. consolidate. beautify. + * @todo the converter should be better named as "validator" since they do not only convert... + * @todo support for std::set<> ? + */ + +#ifndef __I2N_GLOBAL_CONFIG_HPP__ +#define __I2N_GLOBAL_CONFIG_HPP__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace I2n +{ + +namespace Config +{ + +template< + typename ValueType, + class Converter +> +class Var; + + +typedef std::vector< std::string > StringList; + +/** + * @brief contains the "magic" details of the config module + * (i.e. all the stuff which must be declared here, but U shouldn't use it!) + */ +namespace Detail { + + +typedef boost::function< bool(const std::string&) > FeedFunction; +typedef boost::function< bool(const StringList&) > FeedFunction2; + + +struct Thing +{ + virtual ~Thing() {} +}; // eo struct Thing + +typedef boost::shared_ptr< Thing > FlowerShop; + + + + +FlowerShop seymour(const std::string& section, const std::string& key, const FeedFunction& feed); +FlowerShop seymour(const std::string& section, const std::string& key, const FeedFunction2& feed); +FlowerShop seymour(FlowerShop shop, const FeedFunction& feed); +FlowerShop seymour(FlowerShop shop, const FeedFunction2& feed); + +/* +** type magics: +*/ + +/** + * @brief determines if a type is one of the supported list containers. + * @tparam Container the type to test for supported list type. + * + * currently we support std::list<> and std::vector<> (and derivates). + */ +template< class Container > +struct is_list_container +{ + static Container gen(); + + template< typename T > + static boost::type_traits::yes_type c1( const std::vector< T >& ); + + template< typename T > + static boost::type_traits::yes_type c1( const std::list< T >& ); + + static boost::type_traits::no_type c1(...); + + BOOST_STATIC_CONSTANT( bool, value= ( sizeof(c1(gen())) == sizeof(boost::type_traits::yes_type) ) ); +}; + + +template< class C, bool is_list > +struct extract_type_impl +{ + typedef typename boost::remove_reference< typename boost::remove_const< C >::type>::type type; +}; + +template< class C > +struct extract_type_impl< C, true > +{ + typedef typename boost::remove_reference< + typename boost::remove_const< typename C::value_type >::type + >::type type; +}; + +template< class C > +struct extract_type +: public extract_type_impl< C, is_list_container< C >::value > +{ +}; + + +/* +** +*/ + +template< + typename ValueType, + class Converter, + bool wantList +> +class OuterSpace +{ + public: + typedef ValueType result_type; + typedef typename boost::remove_reference< ValueType >::type unref_value_type; + typedef typename boost::remove_const< unref_value_type>::type base_value_type; + typedef typename boost::add_const< + typename boost::add_reference< base_value_type >::type + >::type const_ref_value_type; + + typedef FeedFunction FeedFunctionType; + + protected: + friend class Var; + + OuterSpace(ValueType initial_value) + : Value(initial_value) + , InitialValue(initial_value) + { + } + + OuterSpace(const OuterSpace& other) + : Value( other.Value) + , InitialValue( other.InitialValue) + { + } + + bool audreyII(const std::string& v) + { + bool res= Converter()(v,Value); + if (!res) Value= InitialValue; + return res; + } // eo audreyII(const std::string&) + + base_value_type Value; + base_value_type InitialValue; +}; // eo class OuterSpace + + +template< + typename ValueType, + class Converter +> +class OuterSpace< ValueType, Converter, true > +{ + public: + typedef ValueType result_type; + typedef typename boost::remove_reference< ValueType >::type unref_value_type; + typedef typename boost::remove_const< unref_value_type>::type base_value_type; + typedef typename boost::add_const< + typename boost::add_reference< base_value_type >::type + >::type const_ref_value_type; + + typedef FeedFunction2 FeedFunctionType; + + protected: + friend class Var; + + OuterSpace(ValueType initial_value) + : Value(initial_value) + , InitialValue(initial_value) + { + } + + bool audreyII(const StringList& v) + { + Converter convert; + Value.clear(); + bool res= true; + BOOST_FOREACH(const std::string& s, v) + { + typename base_value_type::value_type value; + if (not convert(s,value)) + { + res = false; + break; + } + Value.push_back( value ); + } + if (not res) Value= InitialValue; + return res; + } // eo audreyII(const std::string&) + + base_value_type Value; + base_value_type InitialValue; +}; // eo class OuterSpace + +} // eo namespace Detail + + +/** + * @brief default converter for the Var class. + * @tparam ValueType the type of the result value (which the input should be converted to) + * + * Uses stringTo to convert the values. + * + * @see i2n::stringTo + */ +template< + typename ValueType +> +struct DefaultConverter +: public std::binary_function< std::string, ValueType&, bool > +{ + + bool operator () ( const std::string& str, ValueType& v ) + { + return string_to< ValueType >( str, v ); + } // eo operator()(const std::string&,ValueType&) + +}; // eo struct DefaultConverter + + +/** + * @brief specialized converter for std::string result type. + * + * just copies the input to the result var and returns true. + */ +template<> struct DefaultConverter< std::string > +: public std::binary_function< std::string, std::string&, bool > +{ + + bool operator () ( const std::string& str, std::string& v ) + { + v= str; + return true; + } // eo operator()(const std::string&,std::string&) + +}; // eo struct DefaultConverter< std::string > + + + +/** + * @brief specialized converter for int types which accepts also octal and hex notation as input. + * @tparam ValueType the type of the result value (which the input should be converted to) + */ +template< + typename ValueType +> +struct AutoIntConverter +: public std::binary_function< std::string, ValueType&, bool > +{ + + bool operator () ( const std::string& str, ValueType& v ) + { + std::istringstream istr(str); + if (has_prefix(str,"0x")) + { + istr.get(); istr.get(); + istr.setf( std::ios::hex, std::ios::basefield ); + } + else if (has_prefix(str,"0") or has_prefix(str,"-0")) + { + istr.setf( std::ios::oct, std::ios::basefield ); + } + istr >> v; + return istr.eof(); + } // eo operator()(const std::string&,ValueType&) + +}; // eo struct AutoIntConverter + + + +/** + * @brief represents a configuration variable with automatic update. + * @tparam ValueType type of the value this variable should hold. + * @tparam Converter a converter class which converts a string (from config file) to the desired + * type. + * Needs to be derived from std::binary_function (taking a string as first arg, a reference to the + * desired type as second and returning bool (@a true if conversion was succesful). + * + * Basic idea is to pass a point (section, key) in the config where the value for this + * variable is stored. The config value (a string) should be converted to the desired value (type). + * When the config is loaded (or reloaded) the var (content) is automatically updated. + * + * @see DefaultConverter + * + * @bug value accessing methods are @a const, but the result type may allow changing the internal value... + */ +template< + typename ValueType, + class Converter = DefaultConverter< + typename Detail::extract_type< ValueType >::type + > +> +class Var +: public Detail::OuterSpace< + ValueType, Converter, + Detail::is_list_container< ValueType >::value > +{ + BOOST_STATIC_ASSERT(( boost::is_base_of< + std::binary_function< + std::string, + typename boost::add_reference< + typename Detail::extract_type< ValueType >::type + >::type, + bool >, + Converter >::value )); + + typedef Detail::OuterSpace< + ValueType, Converter, + Detail::is_list_container< ValueType >::value + > MySpace; + + public: + + typedef typename MySpace::result_type result_type; + typedef typename MySpace::base_value_type base_value_type; + + public: + + /** + * @brief constructor with config location and initial value. + * @param section the section in the config where the value is located in. + * @param key the key (in the section) which identifies the value. + * @param initial_value the initial value. + */ + Var(const std::string& section, + const std::string& key, + ValueType initial_value) + : MySpace(initial_value) + { + FlowerShop= Detail::seymour( + section, key, + typename MySpace::FeedFunctionType( boost::bind( &MySpace::audreyII, this, _1 ) ) ); + } + + + /** + * @brief constructor with config location (key in global section) and initial value. + * @param key the key (in the global section) which identifies the value. + * @param initial_value the initial value. + */ + Var(const std::string& key, + ValueType initial_value) + : MySpace(initial_value) + { + FlowerShop= Detail::seymour( + "", key, + typename MySpace::FeedFunctionType( boost::bind( &MySpace::audreyII, this, _1 ) ) ); + } + + + /** + * @brief operator for accessing the value. + * @return the current value. + */ + result_type operator()() const { return MySpace::Value; } + + + /** + * @brief method for accessing the value. + * @return the current value. + */ + result_type get_value() const { return MySpace::Value; } + + + /** + * @brief copy constructor. + * @param other the instance to copy from. + */ + Var(const Var& other) + : MySpace( other ) + { + FlowerShop= Detail::seymour( + other.FlowerShop, + typename MySpace::FeedFunctionType( boost::bind( &MySpace::audreyII, this, _1 ) ) ); + } // eo Var(const Var&) + + + /** + * @brief cast operator for the underlying value type. delivers the value. + * @return the current value. + * + * The cast operator allows the instance to be used whereever the underlying value type + * is expected. + */ + operator result_type () const { return MySpace::Value; } + + + private: + + Detail::FlowerShop FlowerShop; + +}; // eo class Var + + + +/** + * @brief convenience: \<\< operator which streams the current value of a config var. + */ +template < + typename ValueType, + class Converter +> +std::ostream& operator<<(std::ostream& o, const Var< ValueType, Converter >& v) +{ + return o << v(); +} // eo operator<<(std::ostream&,const Var< ValueType, Converter>& + + +bool set_config_file(const std::string& path); + +std::string get_config_file(); + +bool reload(); + + +} // eo namespace Config + +} // eo namespace I2n + +#endif diff --git a/configure.in b/configure.in index f14562c..0dfdac1 100644 --- a/configure.in +++ b/configure.in @@ -35,4 +35,5 @@ AC_SUBST(LIBICONV_LIBS) AM_PATH_CPPUNIT(1.8.0) -AC_OUTPUT(Doxyfile Makefile src/Makefile libi2ncommon.pc test/Makefile) +AC_OUTPUT(Doxyfile Makefile configlib/Makefile libi2ncommon.pc src/Makefile \ + test/Makefile libi2ncommon_config.pc) diff --git a/libi2ncommon.spec b/libi2ncommon.spec index 21a94d4..9235b41 100644 --- a/libi2ncommon.spec +++ b/libi2ncommon.spec @@ -26,6 +26,18 @@ Requires: boost-devel >= 1.32.0 %description devel library with functions common in Intra2net programs + +%package config +Summary: library with a config module +Group: Intranator +Requires: libi2ncommon = %{version} + +%description config +library with a config module. +The config module provides a global configuration system with decentralized +declaration of the config variables. + + %prep %setup -q @@ -57,3 +69,9 @@ rm -fr $RPM_BUILD_ROOT %{prefix}/lib/*.*a* %{prefix}/lib/pkgconfig/*.pc %{prefix}/include/ + +%files config +%defattr(-,root,root) +%doc LICENSE +%{prefix}/lib/libi2ncommon_config.so* + diff --git a/libi2ncommon_config.pc.in b/libi2ncommon_config.pc.in new file mode 100644 index 0000000..307000f --- /dev/null +++ b/libi2ncommon_config.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libi2ncommon_config +Description: library with config module common in some Intra2net programs +Requires: libi2ncommon +Version: @VERSION@ +Libs: -L${libdir} -li2ncommon_config +Cflags: -I${includedir}