/***************************************************************************** * * This file is part of Mapnik (c++ mapping toolkit) * * Copyright (C) 2006 Artem Pavlenko * * This library 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.1 of the License, or (at your option) any later version. * * This library 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ #ifndef MAPNIK_ENUMERATION_INCLUDED #define MAPNIK_ENUMERATION_INCLUDED #include #include #include #include #include namespace mapnik { class illegal_enum_value : public std::exception { public: illegal_enum_value() {} illegal_enum_value( const std::string & what ) : what_( what ) { } virtual ~illegal_enum_value() throw() {}; virtual const char * what() const throw() { return what_.c_str(); } protected: std::string what_; }; /** Slim wrapper for enumerations. It creates a new type from a native enum and * a char pointer array. It almost exactly behaves like a native enumeration * type. It supports string conversion through stream operators. This is usefull * for debugging, serialization/deserialization and also helps with implementing * language bindings. The two convinient macros DEFINE_ENUM() and IMPLEMENT_ENUM() * are provided to help with instanciation. * * @par Limitations: * - The enum must start at zero. * - The enum must be consecutive. * - The enum must be terminated with a special token consisting of the enum's * name plus "_MAX". * - The corresponding char pointer array must be terminated with an empty string. * - The names must only consist of characters and digits (a-z, A-Z, 0-9), * underscores (_) and dashes (-). * * * @warning At the moment the verify() method is called during static initialization. * It quits the application with exit code 1 if any error is detected. The other solution * i thought of is to do the checks at compile time (using boost::mpl). * * @par Example: * The following code goes into the header file: * @code * enum fruit_enum { * APPLE, * CHERRY, * BANANA, * PASSION_FRUIT, * fruit_enum_MAX * }; * * static const char * fruit_strings[] = { * "apple", * "cherry", * "banana", * "passion_fruit", * "" * }; * * DEFINE_ENUM( fruit, fruit_enum); * @endcode * In the corresponding cpp file do: * @code * IMPLEMENT_ENUM( fruit, fruit_strings ); * @endcode * And here is how to use the resulting type Fruit * @code * * int * main(int argc, char * argv[]) { * fruit f(APPLE); * switch ( f ) { * case BANANA: * case APPLE: * cerr << "No thanks. I hate " << f << "s" << endl; * break; * default: * cerr << "Hmmm ... yummy " << f << endl; * break; * } * * f = CHERRY; * * fruit_enum native_enum = f; * * f.from_string("passion_fruit"); * * for (unsigned i = 0; i < fruit::MAX; ++i) { * cerr << i << " = " << fruit::get_string(i) << endl; * } * * f.from_string("elephant"); // throws illegal_enum_value * * return 0; * } * @endcode */ template class MAPNIK_DECL enumeration { public: typedef ENUM native_type; enumeration() {}; enumeration( ENUM v ) : value_(v) {} enumeration( const enumeration & other ) : value_(other.value_) {} /** Assignment operator for native enum values. */ void operator=(ENUM v) { value_ = v; } /** Assignment operator. */ void operator=(const enumeration & other) { value_ = other.value_; } /** Conversion operator for native enum values. */ operator ENUM() const { return value_; } enum Max { MAX = THE_MAX }; ENUM max() const { return THE_MAX; } /** Converts @p str to an enum. * @throw illegal_enum_value @p str is not a legal identifier. * */ void from_string(const std::string & str) { for (unsigned i = 0; i < THE_MAX; ++i) { if (str == our_strings_[i]) { value_ = static_cast(i); return; } } throw illegal_enum_value(std::string("Illegal enumeration value '") + str + "' for enum " + our_name_); } /** Parses the input stream @p is for a word consisting of characters and * digits (a-z, A-Z, 0-9) and underscores (_). * The failbit of the stream is set if the word is not a valid identifier. */ std::istream & parse(std::istream & is) { std::string word; char c; while ( is.peek() != std::char_traits< char >::eof()) { is >> c; if ( isspace(c) && word.empty() ) { continue; } if ( isalnum(c) || (c == '_') || c == '-' ) { word += c; } else { is.unget(); break; } } try { from_string( word ); } catch (const illegal_enum_value &) { is.setstate(std::ios::failbit); } return is; } /** Returns the current value as a string identifier. */ std::string as_string() const { return our_strings_[value_]; } /** Prints the string identifier to the output stream @p os. */ std::ostream & print(std::ostream & os = std::cerr) const { return os << our_strings_[value_]; } /** Static helper function to iterate over valid identifiers. */ static const char * get_string(unsigned i) { return our_strings_[i]; } /** Performs some simple checks and quits the application if * any error is detected. Tries to print helpful error messages. */ static bool verify(const char * filename, unsigned line_no) { for (unsigned i = 0; i < THE_MAX; ++i) { if (our_strings_[i] == 0 ) { std::cerr << "### FATAL: Not enough strings for enum " << our_name_ << " defined in file '" << filename << "' at line " << line_no << std::endl; std::exit(1); } } if ( std::string("") != our_strings_[THE_MAX]) { std::cerr << "### FATAL: The string array for enum " << our_name_ << " defined in file '" << filename << "' at line " << line_no << " has too many items or is not terminated with an " << "empty string." << std::endl; std::exit(1); } return true; } static const std::string & get_full_qualified_name() { return our_name_; } static std::string get_name() { std::string::size_type idx = our_name_.find_last_of(":"); if ( idx == std::string::npos ) { return our_name_; } else { return our_name_.substr( idx + 1 ); } } private: ENUM value_; static const char ** our_strings_ ; static std::string our_name_ ; static bool our_verified_flag_; }; /** ostream operator for enumeration * @relates mapnik::enumeration */ template std::ostream & operator<<(std::ostream & os, const mapnik::enumeration & e) { e.print( os ); return os; } /** istream operator for enumeration * @relates mapnik::enumeration */ template std::istream & operator>>(std::istream & is, mapnik::enumeration & e) { e.parse( is ); return is; } } // end of namespace /** Helper macro. Creates a typedef. * @relates mapnik::enumeration */ #define DEFINE_ENUM( name, e) \ typedef mapnik::enumeration name /** Helper macro. Runs the verify() method during static initialization. * @relates mapnik::enumeration */ #define IMPLEMENT_ENUM( name, strings ) \ template <> const char ** name ::our_strings_ = strings; \ template <> std::string name ::our_name_ = #name; \ template <> bool name ::our_verified_flag_( name ::verify(__FILE__, __LINE__)); #endif // MAPNIK_ENUMERATION_INCLUDED