Parcourir la source

Initial version of logging module.

First cut at the logging module.  Still lacking configuration and
basic appenders.
Stephen Morris il y a 14 ans
Parent
commit
3666c6d785

+ 15 - 2
configure.ac

@@ -334,10 +334,13 @@ AC_SUBST(USE_LCOV)
 AC_ARG_WITH([log4cxx],
 AC_ARG_WITH([log4cxx],
   AC_HELP_STRING([--with-log4cxx=PATH],
   AC_HELP_STRING([--with-log4cxx=PATH],
     [specify directory where log4cxx is installed]),
     [specify directory where log4cxx is installed]),
-  [log4cxx_include_path="${withval}/include"])
+  [
+   log4cxx_include_path="${withval}/include";
+   log4cxx_lib_path="${$withval}/lib"
+  ])
 
 
 # If not specified, try some common paths.  These default to
 # If not specified, try some common paths.  These default to
-# /usr/include if not found
+# /usr/include and /usr/lib if not found
 
 
 if test -z "$with_log4cxx"; then
 if test -z "$with_log4cxx"; then
 	log4cxxdirs="/usr/local /usr/pkg /opt /opt/local"
 	log4cxxdirs="/usr/local /usr/pkg /opt /opt/local"
@@ -345,6 +348,7 @@ if test -z "$with_log4cxx"; then
 	do
 	do
 		if test -d $d/include/log4cxx; then
 		if test -d $d/include/log4cxx; then
 			log4cxx_include_path=$d/include
 			log4cxx_include_path=$d/include
+            log4cxx_library_path=$d/lib
 			break
 			break
 		fi
 		fi
 	done
 	done
@@ -359,6 +363,13 @@ AC_CHECK_HEADER([log4cxx/logger.h],, AC_MSG_ERROR([Missing log4cxx header files.
 CPPFLAGS="$CPPFLAGS_SAVES"
 CPPFLAGS="$CPPFLAGS_SAVES"
 AC_SUBST(LOG4CXX_INCLUDES)
 AC_SUBST(LOG4CXX_INCLUDES)
 
 
+LOG4CXX_LDFLAGS="-llog4cxx";
+if test "${log4cxx_library_path}"; then
+    LOG4CXX_LDFLAGS="-L{log4cxx_library_path} -llog4cxx"
+fi
+AC_SUBST(LOG4CXX_LDFLAGS)
+
+
 
 
 #
 #
 # Configure Boost header path
 # Configure Boost header path
@@ -645,6 +656,7 @@ AC_CONFIG_FILES([Makefile
                  src/lib/datasrc/tests/Makefile
                  src/lib/datasrc/tests/Makefile
                  src/lib/xfr/Makefile
                  src/lib/xfr/Makefile
                  src/lib/log/Makefile
                  src/lib/log/Makefile
+                 src/lib/log/compiler/Makefile
                  src/lib/log/tests/Makefile
                  src/lib/log/tests/Makefile
                  src/lib/testutils/Makefile
                  src/lib/testutils/Makefile
                  src/lib/testutils/testdata/Makefile
                  src/lib/testutils/testdata/Makefile
@@ -756,6 +768,7 @@ dnl includes too
                  ${PYTHON_LIB}
                  ${PYTHON_LIB}
   Boost:         ${BOOST_INCLUDES}
   Boost:         ${BOOST_INCLUDES}
   log4cxx:       ${LOG4CXX_INCLUDES}
   log4cxx:       ${LOG4CXX_INCLUDES}
+                 ${LOG4CXX_LDFLAGS}
   SQLite:        $SQLITE_CFLAGS
   SQLite:        $SQLITE_CFLAGS
                  $SQLITE_LIBS
                  $SQLITE_LIBS
 
 

+ 1 - 1
src/lib/asiolink/tests/Makefile.am

@@ -21,7 +21,7 @@ run_unittests_SOURCES += asiolink_unittest.cc
 run_unittests_SOURCES += udpdns_unittest.cc
 run_unittests_SOURCES += udpdns_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(LOG4CXX_LDFLAGS)
 run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD += $(SQLITE_LIBS)
 run_unittests_LDADD += $(SQLITE_LIBS)
 run_unittests_LDADD +=  $(top_builddir)/src/lib/dns/libdns++.la
 run_unittests_LDADD +=  $(top_builddir)/src/lib/dns/libdns++.la

+ 13 - 5
src/lib/log/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = . tests
+SUBDIRS = . compiler tests
 
 
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += $(BOOST_INCLUDES)
@@ -7,12 +7,20 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
 CLEANFILES = *.gcno *.gcda
 CLEANFILES = *.gcno *.gcda
 
 
 lib_LTLIBRARIES = liblog.la
 lib_LTLIBRARIES = liblog.la
-liblog_la_SOURCES  = root_logger_name.cc root_logger_name.h
-liblog_la_SOURCES += xdebuglevel.cc xdebuglevel.h
+liblog_la_SOURCES  =
+liblog_la_SOURCES += dbglevels.h
+liblog_la_SOURCES += dummylog.h dummylog.cc Message.h
+liblog_la_SOURCES += filename.h filename.cc
 liblog_la_SOURCES += logger.cc logger.h
 liblog_la_SOURCES += logger.cc logger.h
+liblog_la_SOURCES += messagedef.cc messagedef.h
+liblog_la_SOURCES += message_dictionary.cc message_dictionary.h
+liblog_la_SOURCES += message_exception.h message_exception.cc
+liblog_la_SOURCES += message_initializer.cc message_initializer.h
 liblog_la_SOURCES += message_reader.cc message_reader.h
 liblog_la_SOURCES += message_reader.cc message_reader.h
-liblog_la_SOURCES += filename.h filename.cc
-liblog_la_SOURCES += stringutil.h stringutil.cc
+liblog_la_SOURCES += message_types.h
+liblog_la_SOURCES += root_logger_name.cc root_logger_name.h
+liblog_la_SOURCES += strutil.h strutil.cc
+liblog_la_SOURCES += xdebuglevel.cc xdebuglevel.h
 
 
 # Note: the ordering matters: -Wno-... must follow -Wextra (defined in
 # Note: the ordering matters: -Wno-... must follow -Wextra (defined in
 # B10_CXXFLAGS)
 # B10_CXXFLAGS)

+ 84 - 50
src/lib/log/README.txt

@@ -1,4 +1,23 @@
-Logging Messages
+This directory holds the first release of the logging system.
+
+The steps in using the system are:
+
+1. Create a message file.  Ideally it should have a file type of ".msg".
+2. Run it through the message compiler to produce the .h and .cc files.
+3. Include the .h file in your source code to define message symbols.  Also
+   include the file logger.h to create loggers and to log error messages.
+   The ".cc" file needs to be linked into the program - static initialization
+   will add the symbols to the global dictionary.
+
+
+Outstanding
+===========
+* Ability to configure system according to configuration database.
+* Writing of suitable appenders and formatters.
+* Ability to override message via a run-time file.
+* Update the build procedure to create .cc and .h files from the .msg file
+  during the build process. (Requires that the message compiler is built first.)
+
 
 
 Message Storage
 Message Storage
 ===============
 ===============
@@ -10,14 +29,17 @@ The message identifier (along with parameters) is passed through the logging
 system to an appender, which uses the identifier to look up the message in
 system to an appender, which uses the identifier to look up the message in
 the dictionary.  The message is then formatted and written out.
 the dictionary.  The message is then formatted and written out.
 
 
-Message File
-============
+Message Files
+=============
+
+1) File Contents and Format
 A message file is a file containing message definitions.  Typically there will
 A message file is a file containing message definitions.  Typically there will
 be one message file for each component that declares message symbols.
 be one message file for each component that declares message symbols.
 
 
 
 
-A example file could
-be:
+A example file could be:
+
+-- BEGIN --
 
 
 # Example message file
 # Example message file
 # $ID:$
 # $ID:$
@@ -29,43 +51,54 @@ TEST1       message %s is much too large
 UNKNOWN     unknown message
 UNKNOWN     unknown message
 + Issued when the message is unknown.
 + Issued when the message is unknown.
 
 
+-- END --
+
 Point to note:
 Point to note:
 * Leading and trailing space are trimmed from the line.
 * Leading and trailing space are trimmed from the line.
 * Blank lines are ignored
 * Blank lines are ignored
 * Lines starting with "#" are comments are are ignored.
 * Lines starting with "#" are comments are are ignored.
 * Lines starting $ are directives.  At present, the only directive recognised
 * Lines starting $ are directives.  At present, the only directive recognised
   is $PREFIX, which has one argument: the string used to prefix symbols.  If
   is $PREFIX, which has one argument: the string used to prefix symbols.  If
-  there is no facility directive, there is no prefix to the symbols.
+  there is no facility directive, there is no prefix to the symbols. (Prefixes
+  are explained below.)
 * Lines starting + indicate an explanation for the preceding message.  These
 * Lines starting + indicate an explanation for the preceding message.  These
   are processed by a separate program and used to generate an error messages
   are processed by a separate program and used to generate an error messages
   manual.  However they are treated like comments here.
   manual.  However they are treated like comments here.
-* Message lines.  These comprise a symbol name and a message (which includes
-  C-style substitution strings).
+* Message lines.  These comprise a symbol name and a message, which may
+  include one or more instances of the "%s" the C-style substitution string.
+  (The logging system only recognises the "%s" string.)
 
 
-Message Compiler
-================
-The message compiler produces two files:
+2) Message Compiler
+The message compiler is a program built in the src/log/compiler directory.  It
+processes the message file to produce two files:
 
 
 1) A C++ header file (called <message-file-name>.h) that holds lines of the
 1) A C++ header file (called <message-file-name>.h) that holds lines of the
 form:
 form:
 
 
 namespace {
 namespace {
-const char* PREFIX_IDENTIFIER = "identifier";
+
+isc::log::MessageID PREFIX_IDENTIFIER = "IDENTIFIER";
    :
    :
 
 
 }
 }
 
 
-These are just convenience symbols to avoid the need to type in the string in
-quotes.  PREFIX_ is the string in the $PREFIX directive and is used to avoid
-potential clashes with system-defined symbols.
+The symbols define the keys in the global message dictionary.  At present
+they are defined as std::strings, but a future implementation could redefine
+them as numeric values.
+
+The "PREFIX_" part of the symbol name is the string defined in the $PREFIX
+the argument to the directive.  So "$PREFIX MSG_" would prefix the identifer
+ABC with "MSG_" to give the symbol MSG_ABC.  Similarly "$PREFIX E" would
+prefix it with "E" to give the symbol EABC.
 
 
-2) A C++ source file (called <message-file-name>.cpp) that holds the code
-to insert the symbols and messages into the map.
+
+2) A C++ source file (called <message-file-name>.cpp) that holds the code to
+insert the symbols and messages into the map.
 
 
 This file declares an array of identifiers/messages in the form:
 This file declares an array of identifiers/messages in the form:
 
 
 namespace {
 namespace {
-const char* messages = {
+const char* values[] = {
     identifier1, text1,
     identifier1, text1,
     identifier2, text2,
     identifier2, text2,
     :
     :
@@ -79,41 +112,42 @@ it is not needed.)
 
 
 It then declares an object that will add information to the global dictionary:
 It then declares an object that will add information to the global dictionary:
 
 
-DictionaryAppender <message-file-name>_<prefix>_<time>(messages);
+MessageInitializer <message-file-name>_<time>(values);
 
 
 (Declaring the object as "static" or in the anonymous namespace runs the risk
 (Declaring the object as "static" or in the anonymous namespace runs the risk
 of it being optimised away when the module is compiled with optimisation.  But
 of it being optimised away when the module is compiled with optimisation.  But
 giving it a standard name would cause a clash when multiple files are used,
 giving it a standard name would cause a clash when multiple files are used,
 hence an attempt at disambiguation.)
 hence an attempt at disambiguation.)
 
 
-The constructor of the DictionaryAppender object retrieves the singleton
-global Dictionary object (created using standard methods to avoid the "static
-initialization fiasco") and adds each identifier and text to the member
-std::map.  A check is made as each is added; if the identifier already exists,
-it is added to "overflow" vector; the vector is printed to the main logging
-output when logging is finally enabled (to indicate a programming error).
-
-User Message Files
-==================
-During logging initialization, a search is made for a user message file in a
-specific location.  (The specification of the location has yet to be decided -
-it will probably be a command-line option.)  These messages are read into a
-local Dictionary object (which performs the same checks as the global
-Dictionary for duplicate messages).
-
-The local Dictionary is then merged with the global Dictionary.  In this case
-though, warnings are issued where a message does not replace one in the global
-Dictionary.
-
-An additional check that could be done is to compare the user message string
-with the main message string and to check that they have the same number of
-"%s" components.  This will avoid potential problems in message formatting. (As
-noted in another design document, the intention within logging is to convert
-all parameters to strings at the point at which the logging call is made.)
-
-Message Compiler Implementation
-===============================
-The fact that user files are read in at run-time implies that the code that
-reads the files should be C++.  An implication of this is that the message
-compiler should be written in C++ (instead of Python, which is probably
-better for the task) to avoid two sets of code where message files are parsed.
+The constructor of the MessageInitializer object retrieves the singleton
+global Dictionary object (created using standard methods to avoid the
+"static initialization fiasco") and adds each identifier and text to it.
+A check is made as each is added; if the identifier already exists, it is
+added to "overflow" vector; the vector is printed to the main logging output
+when logging is finally enabled (to indicate a programming error).
+
+Using the Logging
+=================
+To use the current version of the logging:
+
+1. Build message header file and source file as describe above.
+2. In the code that needs to do logging, declare a logger with a given name,
+   e.g.
+
+   #include <log/logger.h>
+        :
+   isc::log::Logger logger("myname");   // "myname" can be anything
+
+3. Issue logging calls using methods on logger, e.g.
+
+   logger.error(DPS_NSTIMEOUT, "isc.org");
+
+   (where, in the example above we might have defined the symbol in the message
+   file with something along the lines of:
+
+   $PREFIX DPS_
+       :
+   NSTIMEOUT  queries to all nameservers for %s have timed out
+
+As noted above, presently the only logging is to the console using the default
+log4cxx format (which is somewhat awkward to read).

+ 21 - 0
src/lib/log/compiler/Makefile.am

@@ -0,0 +1,21 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+CLEANFILES = *.gcno *.gcda
+
+pkglibexec_PROGRAMS = message
+message_SOURCES = message.cc
+message_LDADD  = $(top_builddir)/src/lib/log/liblog.la
+message_LDADD += $(LOG4CXX_LDFLAGS)
+

+ 334 - 8
src/lib/log/compiler/message.cc

@@ -14,11 +14,29 @@
 
 
 // $Id$
 // $Id$
 
 
+#include <cctype>
+#include <fstream>
 #include <iostream>
 #include <iostream>
-#include <unistd.h>
+#include <string>
+#include <vector>
+
+#include <errno.h>
 #include <getopt.h>
 #include <getopt.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <log/filename.h>
+#include <log/message_dictionary.h>
+#include <log/message_exception.h>
+#include <log/message_reader.h>
+#include <log/messagedef.h>
+#include <log/strutil.h>
+
+#include <log/logger.h>
 
 
 using namespace std;
 using namespace std;
+using namespace isc::log;
 
 
 static const char* VERSION = "1.0-0";
 static const char* VERSION = "1.0-0";
 
 
@@ -66,12 +84,292 @@ static void usage() {
         "-h       Print this message and exit\n" <<
         "-h       Print this message and exit\n" <<
         "-p       Output a Python module holding the message definitions.\n" <<
         "-p       Output a Python module holding the message definitions.\n" <<
         "         By default a C++ header file and implementation file are\n" <<
         "         By default a C++ header file and implementation file are\n" <<
+
+
         "         written.\n" <<
         "         written.\n" <<
         "-v       Print the program version and exit\n" <<
         "-v       Print the program version and exit\n" <<
         "\n" <<
         "\n" <<
         "<message-file> is the name of the input message file.\n";
         "<message-file> is the name of the input message file.\n";
 }
 }
 
 
+
+/// \brief Create Time
+///
+/// Returns the current time as a suitably-formatted string.
+///
+/// \return Current time
+
+static string currentTime() {
+
+    // Get the current time.
+    time_t curtime;
+    time(&curtime);
+
+    // Format it
+    char buffer[32];
+    ctime_r(&curtime, buffer);
+
+    // Convert to string and strip out the trailing newline
+    string current_time = buffer;
+    return isc::strutil::trim(current_time);
+}
+
+
+
+
+/// \brief Create Header Sentinel
+///
+/// Given the name of a file, create an #ifdef sentinel name.  The name is
+/// __<name>_<ext>, where <name> is the name of the file, and <ext> is the
+/// extension less the leading period.  The sentinel will be upper-case.
+///
+/// \param file Filename object representing the file.
+///
+/// \return Sentinel name
+
+static string sentinel(Filename& file) {
+
+    string name = file.name();
+    string ext = file.extension();
+    string sentinel_text = "__" + name + "_" + ext.substr(1);
+    isc::strutil::uppercase(sentinel_text);
+    return sentinel_text;
+}
+
+
+/// \brief Quote String
+///
+/// Inserts an escape character (a backslash) prior to any double quote
+/// characters.  This is used to handle the fact that the input file does not
+/// contain quotes, yet the string will be included in a C++ literal string.
+
+string quoteString(const string& instring) {
+
+    // Create the output string and reserve the space needed to hold the input
+    // string. (Most input strings will not contain quotes, so this single
+    // reservation should be all that is needed.) 
+    string outstring;
+    outstring.reserve(instring.size());
+
+    // Iterate through the input string, preceding quotes with a slash.
+    for (size_t i = 0; i < instring.size(); ++i) {
+        if (instring[i] == '"') {
+            outstring += '\\';
+        }
+        outstring += instring[i];
+    }
+
+    return outstring;
+}
+
+
+/// \brief Sorted Identifiers
+///
+/// Given a dictionary, return a vector holding the message IDs in sorted
+/// order.
+///
+/// \param dictionary Dictionary to examine
+///
+/// \return Sorted list of message IDs
+
+vector<MessageID> sortedIdentifiers(MessageDictionary* dictionary) {
+    vector<MessageID> ident;
+
+    for (MessageDictionary::const_iterator i = dictionary->begin();
+         i != dictionary->end(); ++i) {
+        ident.push_back(i->first);
+    }
+    sort(ident.begin(), ident.end());
+
+    return ident;
+}
+
+
+/// \brief Write Header File
+///
+/// Writes the C++ header file containing the symbol definitions.
+///
+/// \param file Name of the message file.  The header file is written to a
+/// file of the same name but with a .h suffix.
+/// \param prefix Prefix string to use in symbols
+/// \param dictionary Dictionary holding the message definitions.
+
+void writeHeaderFile(const string& file, const string& prefix,
+    MessageDictionary* dictionary)
+{
+    Filename message_file(file);
+    Filename header_file(message_file.useAsDefault(".h"));
+
+    // Text to use as the sentinels.
+    string sentinel_text = sentinel(header_file);
+
+    // Open the output file for writing
+    ofstream hfile(header_file.fullName().c_str());
+
+    try {
+        if (hfile.fail()) {
+            throw MessageException(MSG_OPENOUT, header_file.fullName(),
+                strerror(errno));
+        }
+
+        // Write the header preamble.  If there is an error, we'll pick it up
+        // after the last write.
+
+        hfile <<
+            "// File created from " << message_file.fullName() << " on " <<
+                currentTime() << "\n" <<
+             "\n" <<
+             "#ifndef " << sentinel_text << "\n" <<
+             "#define "  << sentinel_text << "\n" <<
+             "\n" <<
+             "#include <log/message_types.h>\n" <<
+             "\n" <<
+             "namespace {\n" <<
+             "\n";
+
+        vector<MessageID> idents = sortedIdentifiers(dictionary);
+        for (vector<MessageID>::const_iterator j = idents.begin();
+            j != idents.end(); ++j) {
+            hfile << "isc::log::MessageID " << prefix << *j <<
+                " = \"" << *j << "\";\n";
+        }
+
+        // ... and finally the postamble
+        hfile <<
+            "\n" <<
+            "} // Anonymous namespace\n" <<
+            "\n" <<
+            "#endif // " << sentinel_text;
+
+        // Report errors (if any) and exit
+        if (hfile.fail()) {
+            throw MessageException(MSG_WRITERR, header_file.fullName(),
+                strerror(errno));
+        }
+
+        hfile.close();
+    }
+    catch (MessageException&) {
+        hfile.close();
+        throw;
+    }
+}
+
+
+/// \brief Convert Non Alpha-Numeric Characters to Underscores
+///
+/// Simple function for use in a call to transform
+
+char replaceNonAlphaNum(char c) {
+    return (isalnum(c) ? c : '_');
+}
+
+
+/// \brief Write Program File
+///
+/// Writes the C++ source code file.  This defines an external objects whose
+/// constructor is run at initialization time.  The constructor adds the message
+/// definitions to the main global dictionary.
+
+void writeProgramFile(const string& file, MessageDictionary* dictionary)
+{
+    Filename message_file(file);
+    Filename program_file(message_file.useAsDefault(".cc"));
+
+    // Open the output file for writing
+    ofstream ccfile(program_file.fullName().c_str());
+    try {
+        if (ccfile.fail()) {
+            throw MessageException(MSG_OPENOUT, program_file.fullName(),
+                strerror(errno));
+        }
+
+        // Write the preamble.  If there is an error, we'll pick it up after
+        // the last write.
+
+        ccfile <<
+            "// File created from " << message_file.fullName() << " on " <<
+                currentTime() << "\n" <<
+             "\n" <<
+             "#include <cstddef>\n" <<
+             "#include <log/message_initializer.h>\n" <<
+             "\n" <<
+             "using namespace isc::log;\n" <<
+             "\n" <<
+             "namespace {\n" <<
+             "\n" <<
+             "const char* values[] = {\n";
+
+        // Output the identifiers and the associated text.
+        vector<MessageID> idents = sortedIdentifiers(dictionary);
+        for (vector<MessageID>::const_iterator i = idents.begin();
+            i != idents.end(); ++i) {
+                ccfile << "    \"" << *i << "\", \"" <<
+                    quoteString(dictionary->getText(*i)) << "\",\n";
+        }
+
+        // ... and the postamble
+        ccfile <<
+            "    NULL\n" <<
+            "};\n" <<
+            "\n" <<
+            "} // Anonymous namespace\n" <<
+            "\n";
+
+        // Now construct a unique name.  We don't put the message initializer as
+        // a static variable or in an anonymous namespace lest the C++
+        // compiler's optimizer decides it can optimise it away.
+        string unique_name = program_file.name() + program_file.extension() +
+            "_" + currentTime();
+        transform(unique_name.begin(), unique_name.end(), unique_name.begin(),
+            replaceNonAlphaNum);
+
+        // ... and write the initialization code
+        ccfile <<
+            "MessageInitializer " << unique_name << "(values);\n";
+
+        // Report errors (if any) and exit
+        if (ccfile.fail()) {
+            throw MessageException(MSG_WRITERR, program_file.fullName(),
+                strerror(errno));
+        }
+
+        ccfile.close();
+    }
+    catch (MessageException&) {
+        ccfile.close();
+        throw;
+    }
+}
+
+
+/// \brief Warn of Duplicate Entries
+///
+/// If the input file contained duplicate message IDs, only the first will be
+/// processed.  However, we should warn about it.
+///
+/// \param dictionary Dictionary containing the message IDs and text.
+
+static void warnDuplicates(MessageDictionary* dictionary) {
+
+    // Get the duplicates (the overflow) and, if present, sort them into some
+    // order and remove those which occur more than once (which mean that they
+    // occur more than twice in the input file).
+    vector<MessageID> duplicates = dictionary->getOverflow();
+    if (duplicates.size() > 0) {
+        cout << "Warning: the following duplicate IDs were found:\n";
+
+        sort(duplicates.begin(), duplicates.end());
+        vector<MessageID>::iterator new_end =
+            unique(duplicates.begin(), duplicates.end());
+        for (vector<MessageID>::iterator i = duplicates.begin();
+            i != new_end; ++i) {
+            cout << "    " << *i << "\n";
+        }
+    }
+}
+
+
 /// \brief Main Program
 /// \brief Main Program
 ///
 ///
 /// Parses the options then dispatches to the appropriate function.  See the
 /// Parses the options then dispatches to the appropriate function.  See the
@@ -113,18 +411,46 @@ int main(int argc, char** argv) {
 
 
     // Do we have the message file?
     // Do we have the message file?
     if (optind < (argc - 1)) {
     if (optind < (argc - 1)) {
-        std::cout << "Error: excess arguments in command line\n";
+        cout << "Error: excess arguments in command line\n";
         usage();
         usage();
         return 1;
         return 1;
-    }
-    else if (optind >= argc) {
-        std::cout << "Error: missing message file\n";
+    } else if (optind >= argc) {
+        cout << "Error: missing message file\n";
         usage();
         usage();
         return 1;
         return 1;
     }
     }
+    string message_file = argv[optind];
+
+    try {
+        // Have identified the file, so process it.  First create a local
+        // dictionary into which the data will be put.
+        MessageDictionary dictionary;
+
+        // Read the data into it.
+        MessageReader reader(&dictionary);
+        reader.readFile(message_file);
+
+        // Now write the header file.
+        writeHeaderFile(message_file, reader.getPrefix(), &dictionary);
+
+        // ... and the message text file.
+        writeProgramFile(message_file, &dictionary);
+
+        // Finally, warn of any duplicates encountered.
+        warnDuplicates(&dictionary);
+    }
+    catch (MessageException& e) {
+        // Create an error message from the ID and the text
+        MessageDictionary* global = MessageDictionary::globalDictionary();
+        string text = e.id() + ", " + global->getText(e.id());
+
+        // Format with arguments
+        text = isc::strutil::format(text, e.arguments());
+        cerr << text << "\n";
+
+        return 1;
+    }
 
 
-    // Have identified the file, so process it.
-    MessageFileProcessor fileProcessor();
-    return fileProcessor.process(argv[argc - 1], python);
+    return 0;
 
 
 }
 }

+ 1 - 1
src/lib/log/dbglevels.h

@@ -22,7 +22,7 @@
 /// Defines the maximum and minimum debug levels and the number of levels.
 /// Defines the maximum and minimum debug levels and the number of levels.
 /// These are defined using #define as they are referenced in the construction
 /// These are defined using #define as they are referenced in the construction
 /// of variables declared outside execution units.  (In this way we avoid the
 /// of variables declared outside execution units.  (In this way we avoid the
-/// "static initialization fiasco" problem.
+/// "static initialization fiasco" problem.)
 
 
 #define MIN_DEBUG_LEVEL 0
 #define MIN_DEBUG_LEVEL 0
 #define MAX_DEBUG_LEVEL 99
 #define MAX_DEBUG_LEVEL 99

+ 6 - 5
src/lib/log/filename.cc

@@ -20,7 +20,8 @@
 
 
 #include <ctype.h>
 #include <ctype.h>
 
 
-#include <filename.h>
+#include <log/filename.h>
+#include <log/strutil.h>
 
 
 using namespace std;
 using namespace std;
 
 
@@ -90,8 +91,8 @@ string Filename::expandWithDefault(const string& defname) const {
     string def_extension("");
     string def_extension("");
 
 
     // Normalize the input string.
     // Normalize the input string.
-    string copy_defname = StringUtil::trim(defname);
-    StringUtil::normalizeSlash(copy_defname);
+    string copy_defname = isc::strutil::trim(defname);
+    isc::strutil::normalizeSlash(copy_defname);
 
 
     // Split into the components
     // Split into the components
     split(copy_defname, def_directory, def_name, def_extension);
     split(copy_defname, def_directory, def_name, def_extension);
@@ -113,8 +114,8 @@ string Filename::useAsDefault(const string& name) const {
     string name_extension("");
     string name_extension("");
 
 
     // Normalize the input string.
     // Normalize the input string.
-    string copy_name = StringUtil::trim(name);
-    StringUtil::normalizeSlash(copy_name);
+    string copy_name = isc::strutil::trim(name);
+    isc::strutil::normalizeSlash(copy_name);
 
 
     // Split into the components
     // Split into the components
     split(copy_name, name_directory, name_name, name_extension);
     split(copy_name, name_directory, name_name, name_extension);

+ 3 - 3
src/lib/log/filename.h

@@ -19,7 +19,7 @@
 
 
 #include <string>
 #include <string>
 
 
-#include <stringutil.h>
+#include <strutil.h>
 
 
 namespace isc {
 namespace isc {
 namespace log {
 namespace log {
@@ -71,9 +71,9 @@ public:
     ///
     ///
     /// \param name New name to replaced currently stored name
     /// \param name New name to replaced currently stored name
     void setName(const std::string& name) {
     void setName(const std::string& name) {
-        full_name_ = StringUtil::trim(name);
+        full_name_ = isc::strutil::trim(name);
 #ifdef WIN32
 #ifdef WIN32
-        StringUtil::normalizeSlash(full_name_);
+        isc::strutil::normalizeSlash(full_name_);
 #endif
 #endif
         split(full_name_, directory_, name_, extension_);
         split(full_name_, directory_, name_, extension_);
     }
     }

+ 89 - 1
src/lib/log/logger.cc

@@ -18,13 +18,20 @@
 
 
 #include <log/root_logger_name.h>
 #include <log/root_logger_name.h>
 #include <log/logger.h>
 #include <log/logger.h>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+#include <log/strutil.h>
 #include <log/xdebuglevel.h>
 #include <log/xdebuglevel.h>
 
 
+#include <log4cxx/basicconfigurator.h>
+
 using namespace std;
 using namespace std;
 
 
 namespace isc {
 namespace isc {
 namespace log {
 namespace log {
 
 
+bool Logger::init_ = false;
+
 // Constructor - create a logger as a child of the root logger.  With log4cxx
 // Constructor - create a logger as a child of the root logger.  With log4cxx
 // this is assured by naming the logger <parent>.<child>.
 // this is assured by naming the logger <parent>.<child>.
 
 
@@ -38,6 +45,12 @@ Logger::Logger(const std::string& name) : loggerptr_()
         fullname_ = root_name + "." + name;
         fullname_ = root_name + "." + name;
     }
     }
     loggerptr_ = log4cxx::Logger::getLogger(fullname_);
     loggerptr_ = log4cxx::Logger::getLogger(fullname_);
+
+    // Initialize basic logging if not already done
+    if (! init_) {
+        log4cxx::BasicConfigurator::configure();
+        init_ = true;
+    }
 }
 }
 
 
 // Set the severity for logging.  There is a 1:1 mapping between the logging
 // Set the severity for logging.  There is a 1:1 mapping between the logging
@@ -180,8 +193,83 @@ int Logger::getDebugLevel() const {
             return (0);
             return (0);
         }
         }
     }
     }
-             
 }
 }
 
 
+// Return formatted message
+// THIS IS A PLACE HOLDER
+
+string
+Logger::formatMessage(MessageID ident, vector<string>* args) {
+
+    // Return the format string
+    MessageDictionary* global = MessageDictionary::globalDictionary();
+    string text = global->getText(ident);
+
+    // Do argument substitution if there are any
+    if (args) {
+        text = isc::strutil::format(text, *args);
+    }
+
+    return ident + ", " + text;
+}
+
+string
+Logger::formatMessage(MessageID ident, const string& composite) {
+    vector<string> args = isc::strutil::tokens(composite, "\0");
+    return formatMessage(ident, &args);
+}
+
+
+// Debug methods
+
+void Logger::debugCommon(MessageID ident, const std::string* args) {
+    if (args) {
+        LOG4CXX_DEBUG(loggerptr_, formatMessage(ident, *args));
+    } else {
+        LOG4CXX_DEBUG(loggerptr_, formatMessage(ident));
+    }
+}
+
+// Info
+
+void Logger::infoCommon(MessageID ident, const std::string* args) {
+    if (args) {
+        LOG4CXX_INFO(loggerptr_, formatMessage(ident, *args));
+    } else {
+        LOG4CXX_INFO(loggerptr_, formatMessage(ident));
+    }
+}
+
+// Warning
+
+void Logger::warnCommon(MessageID ident, const std::string* args) {
+    if (args) {
+        LOG4CXX_WARN(loggerptr_, formatMessage(ident, *args));
+    } else {
+        LOG4CXX_WARN(loggerptr_, formatMessage(ident));
+    }
+}
+
+// Error
+
+void Logger::errorCommon(MessageID ident, const std::string* args) {
+    if (args) {
+        LOG4CXX_ERROR(loggerptr_, formatMessage(ident, *args));
+    } else {
+        LOG4CXX_ERROR(loggerptr_, formatMessage(ident));
+    }
+}
+
+// Fatal
+
+void Logger::fatalCommon(MessageID ident, const std::string* args) {
+    if (args) {
+        LOG4CXX_FATAL(loggerptr_, formatMessage(ident, *args));
+    } else {
+        LOG4CXX_FATAL(loggerptr_, formatMessage(ident));
+    }
+}
+
+
 } // namespace log
 } // namespace log
 } // namespace isc
 } // namespace isc

+ 184 - 128
src/lib/log/logger.h

@@ -22,6 +22,7 @@
 #include <log4cxx/logger.h>
 #include <log4cxx/logger.h>
 
 
 #include <log/dbglevels.h>
 #include <log/dbglevels.h>
+#include <log/message_types.h>
 
 
 namespace isc {
 namespace isc {
 namespace log {
 namespace log {
@@ -29,8 +30,6 @@ namespace log {
 class Logger {
 class Logger {
 public:
 public:
 
 
-    typedef int MessageCode;    ///< Type of the message code
-
     /// \brief Severity Levels
     /// \brief Severity Levels
     typedef enum {
     typedef enum {
         DEFAULT,    // Default to logging level of parent
         DEFAULT,    // Default to logging level of parent
@@ -70,7 +69,7 @@ public:
     ///
     ///
     /// \param name Name of the logger.  If the name is that of the root name,
     /// \param name Name of the logger.  If the name is that of the root name,
     /// this creates an instance of the root logger; otherwise it creates a
     /// this creates an instance of the root logger; otherwise it creates a
-    /// chold of the root logger.
+    /// child of the root logger.
     Logger(const std::string& name);
     Logger(const std::string& name);
 
 
     /// \brief Get Name of Logger
     /// \brief Get Name of Logger
@@ -188,51 +187,90 @@ public:
     /// not have a level set, the inheritance tree is traversed until a level
     /// not have a level set, the inheritance tree is traversed until a level
     /// is found.
     /// is found.
     virtual Level getEffectiveLevel() const;
     virtual Level getEffectiveLevel() const;
+*/
+
+    // NOTE - THE FOLLOWING ARE TEMPORARY
+    //
+    // Until properly integrated into log4cxx, the following formatting methods
+    // are used. (It is this that explains why the arguments to the logging
+    // messages are concatenated into a single string only to be broken up
+    // again in the formatting code.)
+
+
+    /// \brief Basic Message Formatting
+    ///
+    /// Extracts a message from the global dictionary and substitutes
+    /// arguments (if any).
+    ///
+    /// \param ident Message identifier
+    /// \param args Pointer to an argument vector or NULL if none.
+    ///
+    /// \return Formatted message.
+    std::string formatMessage(MessageID ident,
+        std::vector<std::string>* args = NULL);
+
+    /// \brief Basic Message Formatting
+    ///
+    /// Extracts a message from the global dictionary and substitutes
+    /// arguments (if any).
+    ///
+    /// \param ident Message identifier
+    /// \param args Set of arguments as a single string separated by the NULL
+    /// character.
+    ///
+    /// \return Formatted message.
+    std::string formatMessage(MessageID ident, const std::string& args);
+
+
     /// \brief Debug Messages
     /// \brief Debug Messages
     ///
     ///
     /// A set of functions that control the output of the message and up to
     /// A set of functions that control the output of the message and up to
     /// four parameters.
     /// four parameters.
-    void debugCommon(MessageCode code, std::string arg);
+
+    void debugCommon(MessageID ident, const std::string* args = NULL);
+
+    void debug(MessageID ident) {
+        if (isDebugEnabled()) {
+            debugCommon(ident);
+        }
+    }
 
 
     template <typename T1>
     template <typename T1>
-    void debug(Level level, MessageCode code, T1 arg1) {
-        if (shouldOutputDebug(level)) {
-            debugCommon(code,
-                boost::lexical_cast<std::string>(arg1)
-            );
+    void debug(MessageID ident, T1 arg1) {
+        if (isDebugEnabled()) {
+            std::string args = boost::lexical_cast<std::string>(arg1);
+            debugCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2>
     template <typename T1, typename T2>
-    void debug(MessageCode code, T1 arg1, T2 arg2) {
-        if (shouldOutputDebug(level)) {
-            debugCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2)
-            );
+    void debug(MessageID ident, T1 arg1, T2 arg2) {
+        if (isDebugEnabled()) {
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2);
+            debugCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2, typename T3>
     template <typename T1, typename T2, typename T3>
-    void debug(MessageCode code, T1 arg1, T2 arg2, T3 arg3) {
-        if (shouldOutputDebug(level)) {
-            debugCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg3)
-            );
+    void debug(MessageID ident, T1 arg1, T2 arg2, T3 arg3) {
+        if (isDebugEnabled()) {
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg3);
+            debugCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2, typename T3, typename T4>
     template <typename T1, typename T2, typename T3, typename T4>
-    void debug(MessageCode code, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
-        if (shouldOutputDebug(level)) {
-            debugCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg3) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg4)
-            );
+    void debug(MessageID ident, T1 arg1, T2 arg2, T3 arg3,
+        T4 arg4) {
+        if (isDebugEnabled()) {
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg3) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg4);
+            debugCommon(ident, &args);
         }
         }
     }
     }
 
 
@@ -240,47 +278,50 @@ public:
     ///
     ///
     /// A set of functions that control the output of the message and up to
     /// A set of functions that control the output of the message and up to
     /// four parameters.
     /// four parameters.
-    void infoCommon(MessageCode code, std::string arg);
+
+    void infoCommon(MessageID ident, const std::string* args = NULL);
+
+    void info(MessageID ident) {
+        if (isInfoEnabled()) {
+            infoCommon(ident);
+        }
+    }
 
 
     template <typename T1>
     template <typename T1>
-    void info(MessageCode code, T1 arg1) {
+    void info(MessageID ident, T1 arg1) {
         if (isInfoEnabled()) {
         if (isInfoEnabled()) {
-            infoCommon(code,
-                boost::lexical_cast<std::string>(arg1)
-            );
+            std::string args = boost::lexical_cast<std::string>(arg1);
+            infoCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2>
     template <typename T1, typename T2>
-    void info(MessageCode code, T1 arg1, T2 arg2) {
+    void info(MessageID ident, T1 arg1, T2 arg2) {
         if (isInfoEnabled()) {
         if (isInfoEnabled()) {
-            infoCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2)
-            );
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2);
+            infoCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2, typename T3>
     template <typename T1, typename T2, typename T3>
-    void info(MessageCode code, T1 arg1, T2 arg2, T3 arg3) {
+    void info(MessageID ident, T1 arg1, T2 arg2, T3 arg3) {
         if (isInfoEnabled()) {
         if (isInfoEnabled()) {
-            infoCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg3)
-            );
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg3);
+            infoCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2, typename T3, typename T4>
     template <typename T1, typename T2, typename T3, typename T4>
-    void info(MessageCode code, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
+    void info(MessageID ident, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
         if (isInfoEnabled()) {
         if (isInfoEnabled()) {
-            infoCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg3) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg4)
-            );
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg3) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg4);
+            infoCommon(ident, &args);
         }
         }
     }
     }
 
 
@@ -288,47 +329,50 @@ public:
     ///
     ///
     /// A set of functions that control the output of the message and up to
     /// A set of functions that control the output of the message and up to
     /// four parameters.
     /// four parameters.
-    void warnCommon(MessageCode code, std::string arg);
+
+    void warnCommon(MessageID ident, const std::string* args = NULL);
+
+    void warn(MessageID ident) {
+        if (isWarnEnabled()) {
+            warnCommon(ident);
+        }
+    }
 
 
     template <typename T1>
     template <typename T1>
-    void warn(MessageCode code, T1 arg1) {
+    void warn(Severity severity, MessageID ident, T1 arg1) {
         if (isWarnEnabled()) {
         if (isWarnEnabled()) {
-            warnCommon(code,
-                boost::lexical_cast<std::string>(arg1)
-            );
+            std::string args = boost::lexical_cast<std::string>(arg1);
+            warnCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2>
     template <typename T1, typename T2>
-    void warn(MessageCode code, T1 arg1, T2 arg2) {
+    void warn(MessageID ident, T1 arg1, T2 arg2) {
         if (isWarnEnabled()) {
         if (isWarnEnabled()) {
-            warnCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2)
-            );
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2);
+            warnCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2, typename T3>
     template <typename T1, typename T2, typename T3>
-    void warn(MessageCode code, T1 arg1, T2 arg2, T3 arg3) {
+    void warn(MessageID ident, T1 arg1, T2 arg2, T3 arg3) {
         if (isWarnEnabled()) {
         if (isWarnEnabled()) {
-            warnCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg3)
-            );
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg3);
+            warnCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2, typename T3, typename T4>
     template <typename T1, typename T2, typename T3, typename T4>
-    void warn(MessageCode code, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
+    void warn(MessageID ident, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
         if (isWarnEnabled()) {
         if (isWarnEnabled()) {
-            warnCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg3) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg4)
-            );
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg3) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg4);
+            warnCommon(ident, &args);
         }
         }
     }
     }
 
 
@@ -336,98 +380,103 @@ public:
     ///
     ///
     /// A set of functions that control the output of the message and up to
     /// A set of functions that control the output of the message and up to
     /// four parameters.
     /// four parameters.
-    void errorCommon(MessageCode code, std::string arg);
+
+    void errorCommon(MessageID ident, const std::string* args = NULL);
+
+    void error(MessageID ident) {
+        if (isErrorEnabled()) {
+            errorCommon(ident);
+        }
+    }
 
 
     template <typename T1>
     template <typename T1>
-    void error(MessageCode code, T1 arg1) {
+    void error(Severity severity, MessageID ident, T1 arg1) {
         if (isErrorEnabled()) {
         if (isErrorEnabled()) {
-            errorCommon(code,
-                boost::lexical_cast<std::string>(arg1)
-            );
+            std::string args = boost::lexical_cast<std::string>(arg1);
+            errorCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2>
     template <typename T1, typename T2>
-    void error(MessageCode code, T1 arg1, T2 arg2) {
+    void error(MessageID ident, T1 arg1, T2 arg2) {
         if (isErrorEnabled()) {
         if (isErrorEnabled()) {
-            errorCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2)
-            );
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2);
+            errorCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2, typename T3>
     template <typename T1, typename T2, typename T3>
-    void error(MessageCode code, T1 arg1, T2 arg2, T3 arg3) {
+    void error(MessageID ident, T1 arg1, T2 arg2, T3 arg3) {
         if (isErrorEnabled()) {
         if (isErrorEnabled()) {
-            errorCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg3)
-            );
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg3);
+            errorCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2, typename T3, typename T4>
     template <typename T1, typename T2, typename T3, typename T4>
-    void error(MessageCode code, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
+    void error(MessageID ident, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
         if (isErrorEnabled()) {
         if (isErrorEnabled()) {
-            errorCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg3) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg4)
-            );
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg3) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg4);
+            errorCommon(ident, &args);
         }
         }
     }
     }
 
 
-    /// \brief Critical Messages
+    /// \brief Fatal Messages
     ///
     ///
     /// A set of functions that control the output of the message and up to
     /// A set of functions that control the output of the message and up to
     /// four parameters.
     /// four parameters.
-    void criticalCommon(MessageCode code, std::string arg);
+
+    void fatalCommon(MessageID ident, const std::string* args = NULL);
+
+    void fatal(MessageID ident) {
+        if (isFatalEnabled()) {
+            fatalCommon(ident);
+        }
+    }
 
 
     template <typename T1>
     template <typename T1>
-    void critical(MessageCode code, T1 arg1) {
-        if (isCriticalEnabled()) {
-            criticalCommon(code,
-                boost::lexical_cast<std::string>(arg1)
-            );
+    void fatal(Severity severity, MessageID ident, T1 arg1) {
+        if (isFatalEnabled()) {
+            std::string args = boost::lexical_cast<std::string>(arg1);
+            fatalCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2>
     template <typename T1, typename T2>
-    void critical(MessageCode code, T1 arg1, T2 arg2) {
-        if (isCriticalEnabled()) {
-            criticalCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2)
-            );
+    void fatal(MessageID ident, T1 arg1, T2 arg2) {
+        if (isFatalEnabled()) {
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2);
+            fatalCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2, typename T3>
     template <typename T1, typename T2, typename T3>
-    void critical(MessageCode code, T1 arg1, T2 arg2, T3 arg3) {
-        if (isCriticalEnabled()) {
-            criticalCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg3)
-            );
+    void fatal(MessageID ident, T1 arg1, T2 arg2, T3 arg3) {
+        if (isFatalEnabled()) {
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg3);
+            fatalCommon(ident, &args);
         }
         }
     }
     }
 
 
     template <typename T1, typename T2, typename T3, typename T4>
     template <typename T1, typename T2, typename T3, typename T4>
-    void critical(MessageCode code, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
-        if (isCriticalEnabled()) {
-            errorCommon(code,
-                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg3) + std::string('\0') +
-                boost::lexical_cast<std::string>(arg4)
-            );
+    void fatal(MessageID ident, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
+        if (isFatalEnabled()) {
+            std::string args = boost::lexical_cast<std::string>(arg1) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg2) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg3) +
+                std::string("\0") + boost::lexical_cast<std::string>(arg4);
+            fatalCommon(ident, &args);
         }
         }
     }
     }
-*/
 
 
 protected:
 protected:
 
 
@@ -478,9 +527,16 @@ protected:
     /// \return BIND-10 logging severity
     /// \return BIND-10 logging severity
     Severity convertLevel(int value) const;
     Severity convertLevel(int value) const;
 
 
+    /// \brief Formats Message
+    ///
+    /// Receives a message in the form of 
+
 private:
 private:
     log4cxx::LoggerPtr  loggerptr_; ///< Pointer to the underlying logger
     log4cxx::LoggerPtr  loggerptr_; ///< Pointer to the underlying logger
     std::string         fullname_;  ///< Full name of this logger
     std::string         fullname_;  ///< Full name of this logger
+
+    // NOTE - THIS IS A PLACE HOLDER
+    static bool         init_;      ///< Set true when initialized
 };
 };
 
 
 } // namespace log
 } // namespace log

+ 113 - 0
src/lib/log/message_dictionary.cc

@@ -0,0 +1,113 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <cstddef>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// (Virtual) Destructor
+
+MessageDictionary::~MessageDictionary() {
+}
+
+// Add message and note if ID already exists
+
+bool MessageDictionary::add(const MessageID& ident, const std::string& text)
+{
+    map<MessageID, string>::iterator i = dictionary_.find(ident);
+
+    if (i == dictionary_.end()) {
+
+        // Not found, so add it
+        dictionary_[ident] = text;
+        return true;
+    }
+    else {
+
+        // Exists, so add the ID to the overflow vector.
+        overflow_.push_back(ident);
+        return false;
+    }
+}
+
+// Add message and note if ID does not already exist
+
+bool MessageDictionary::replace(const MessageID& ident, const std::string& text)
+{
+    map<MessageID, string>::iterator i = dictionary_.find(ident);
+
+    if (i != dictionary_.end()) {
+
+        // Exists, so replace it.
+        dictionary_[ident] = text;
+        return true;
+    }
+    else {
+
+        // Not found, so add to the overflow vector.
+        overflow_.push_back(ident);
+        return false;
+    }
+}
+
+// Load a set of messages
+
+void MessageDictionary::load(const char* messages[]) {
+    int i = 0;
+    while (messages[i]) {
+        MessageID ident(messages[i]);
+        ++i;
+        if (messages[i]) {
+            string text(messages[i]);
+            add(ident, text);
+            ++i;
+        }
+    }
+}
+
+// Return message text or blank string
+
+string MessageDictionary::getText(const MessageID& ident) const {
+    map<MessageID, string>::const_iterator i = dictionary_.find(ident);
+    if (i == dictionary_.end()) {
+        return string("");
+    }
+    else {
+        return i->second;
+    }
+}
+
+// Return global dictionary
+
+MessageDictionary* MessageDictionary::globalDictionary() {
+    static MessageDictionary* global = NULL;
+
+    if (global == NULL) {
+        global = new MessageDictionary();
+    }
+    return global;
+}
+
+
+
+
+} // namspace log
+} // namespace isc

+ 169 - 0
src/lib/log/message_dictionary.h

@@ -0,0 +1,169 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __MESSAGE_DICTIONARY_H
+#define __MESSAGE_DICTIONARY_H
+
+#include <cstddef>
+#include <string>
+#include <map>
+#include <vector>
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Message Dictionary
+///
+/// The message dictionary is a wrapper around a std::map object, and allows
+/// message text to be retrieved given the string identification.
+///
+/// Adding text occurs in two modes:
+///
+/// Through the "Add" method, ID/text mappings are added to the dictionary
+/// unless the ID already exists.  If so, the ID is added to an "overflow"
+/// vector from where it can be retrieved later.
+///
+/// Through the "Replace" method, ID/text mappings are added to the dictionary
+/// only if the ID already exists.  Otherwise the ID is added to the overflow
+/// vector.
+///
+/// The "Add" method is designed for initialization of the program with the
+/// text supplied by the developers.  Here the message IDs must be unique.
+/// The "Replace" method is for use when a message file is supplied to replace
+/// messages provided with the program.  The supplied messages in this case
+/// should replace the ones that come with the program.
+///
+/// Although the class can be used stand-alone, it does supply a static method
+/// to return a particular instance - the "global" dictionary.
+
+class MessageDictionary {
+public:
+
+    // Default constructor and assignment operator are OK for this class
+
+    /// \brief Virtual Destructor
+    virtual ~MessageDictionary();
+
+    /// \brief Add Message
+    ///
+    /// Adds a message to the dictionary.  If the ID already exists, the ID is
+    /// added to the overflow vector.
+    ///
+    /// \param ident Identification of the message to add
+    /// \param text Message text
+    ///
+    /// \return true if the message was added to the dictionary, false if the
+    /// message existed and it was added to the overflow vector.
+    virtual bool add(const MessageID& ident, const std::string& text);
+
+
+    /// \brief Replace Message
+    ///
+    /// Replaces a message in the dictionary.  If the ID does not exist, it is
+    /// added to the overflow vector.
+    ///
+    /// \param ident Identification of the message to replace
+    /// \param text Message text
+    ///
+    /// \return true if the message was added to the dictionary, false if the
+    /// message did not exist and it was added to the overflow vector.
+    virtual bool replace(const MessageID& ident, const std::string& text);
+
+
+    /// \brief Load Dictionary
+    ///
+    /// Designed to be used during the initialization of programs, this
+    /// accepts a set of (ID, text) pairs as a one-dimensional array of
+    /// const char* and adds them to the dictionary.  The messages are added
+    /// using "Add".
+    ///
+    /// \param data null-terminated array of const char* alternating ID and
+    /// message text.  This should be an odd number of elements long, the last
+    /// elemnent being NULL.  If it is an even number of elements long, the
+    /// last ID is ignored.
+    virtual void load(const char* elements[]);
+
+
+    /// \brief Get Message Text
+    ///
+    /// Given an ID, retrieve associated message text.
+    ///
+    /// \param ident Message identification
+    ///
+    /// \return Text associated with message or empty string if the ID is not
+    /// recognised.  (Note: this precludes an ID being associated with an empty
+    /// string.)
+    virtual std::string getText(const MessageID& ident) const;
+
+
+    /// \brief Clear Overflow
+    ///
+    /// Clears the overflow vector, perhaps because new definitions are going
+    /// to be added.
+    virtual void clearOverflow() {
+        overflow_.clear();
+    }
+
+
+    /// \brief Return Overflow Vector
+    ///
+    /// Returns the overflow vector.
+    ///
+    /// \return Overflow vector
+    virtual std::vector<MessageID> getOverflow() const {
+        return overflow_;
+    }
+
+
+    /// \brief Number of Items in Dictionary
+    ///
+    /// \return Number of items in the dictionary
+    virtual size_t size() const {
+        return dictionary_.size();
+    }
+
+    // Allow access to the internal map structure, but don't allow alteration.
+    typedef std::map<MessageID, std::string>::const_iterator const_iterator;
+
+    /// \brief Return begin() iterator of internal map
+    const_iterator begin() const {
+        return dictionary_.begin();
+    }
+
+    /// \brief Return end() iterator of internal map
+    const_iterator end() const {
+        return dictionary_.end();
+    }
+
+
+    /// \brief Return Global Dictionary
+    ///
+    /// Returns a pointer to the singleton global dictionary.
+    ///
+    /// \return Pointer to global dictionary.
+    static MessageDictionary* globalDictionary();
+
+private:
+    std::map<MessageID, std::string>  dictionary_;
+    std::vector<MessageID>            overflow_;
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGE_DICTIONARY_H

+ 28 - 0
src/lib/log/message_exception.cc

@@ -0,0 +1,28 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+/// \brief Body of Virtual Destructor
+
+#include <log/message_exception.h>
+
+namespace isc {
+namespace log {
+
+MessageException::~MessageException() throw() {
+}
+
+} // namespace log
+} // namespace isc

+ 90 - 0
src/lib/log/message_exception.h

@@ -0,0 +1,90 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __MESSAGE_EXCEPTION_H
+#define __MESSAGE_EXCEPTION_H
+
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Message Exception
+///
+/// Used in the message reader, this simple exception class allows a message
+/// code and its arguments to be encapsulated in an exception and thrown
+/// up the stack.
+
+class MessageException : public std::exception {
+public:
+
+    /// \brief Constructor
+    ///
+    /// \param id Message identification
+    MessageException(MessageID id) : id_(id)
+    {}
+
+    /// \brief Constructor
+    ///
+    /// \param id Message identification
+    /// \param arg1 First message argument
+    MessageException(MessageID id, const std::string& arg1) : id_(id)
+    {
+        args_.push_back(arg1);
+    }
+
+    /// \brief Constructor
+    ///
+    /// \param id Message identification
+    /// \param arg1 First message argument
+    /// \param arg2 Second message argument
+    MessageException(MessageID id, const std::string& arg1,
+        const std::string& arg2) : id_(id)
+    {
+        args_.push_back(arg1);
+        args_.push_back(arg2);
+    }
+
+    /// \brief Destructor
+    virtual ~MessageException() throw();
+
+    /// \brief Return Message ID
+    ///
+    /// \return Message identification
+    MessageID id() const {
+        return id_;
+    }
+
+    /// \brief Return Arguments
+    ///
+    /// \return Exception Arguments
+    std::vector<std::string> arguments() const {
+        return args_;
+    }
+
+private:
+    MessageID                   id_;        // Exception ID
+    std::vector<std::string>    args_;      // Exception arguments
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGE_EXCEPTION_H

+ 32 - 0
src/lib/log/message_initializer.cc

@@ -0,0 +1,32 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <log/message_dictionary.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace log {
+
+// Constructor.  Just retrieve the global dictionary and load the IDs and
+// associated text into it.
+
+MessageInitializer::MessageInitializer(const char* values[]) {
+    MessageDictionary* global = MessageDictionary::globalDictionary();
+    global->load(values);
+}
+
+} // namespace log
+} // namespace isc

+ 63 - 0
src/lib/log/message_initializer.h

@@ -0,0 +1,63 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __MESSAGEINITIALIZER_H
+#define __MESSAGEINITIALIZER_H
+
+#include <log/message_dictionary.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Initialize Message Dictionary
+///
+/// This is a helper class to add a set of message IDs and associated text to
+/// the global dictionary.
+///
+/// It should be declared outside an execution unit and initialized with a
+/// an array of values, alternating identifier, associated text and ending with
+/// a NULL, e.g.
+///
+///     static const char* values[] = {
+///         "IDENT1", "message for ident 1",
+///         "IDENT2", "message for ident 2",
+///             :
+///         NULL
+///     };
+///     MessageDictionaryHelper xyz(values);
+///
+/// This will automatically add the message ID/text pairs to the global
+/// dictionary during initialization - all that is required is that the module
+/// containing the definition is included into the final executable.
+///
+/// Messages are added via the MessageDictionary::add() method, so any
+/// duplicates are stored in the the global dictionary's overflow vector whence
+/// they can be retrieved at run-time.
+
+class MessageInitializer {
+public:
+
+    /// \brief Constructor
+    ///
+    /// The only method in the class, this adds the array of values to the
+    /// global dictionary.
+    MessageInitializer(const char* values[]);
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGEINITIALIZER_H

+ 94 - 44
src/lib/log/message_reader.cc

@@ -14,8 +14,16 @@
 
 
 // $Id$
 // $Id$
 
 
+#include <errno.h>
+#include <string.h>
+
+#include <iostream>
+#include <fstream>
+
+#include <log/message_exception.h>
+#include <log/messagedef.h>
 #include <log/message_reader.h>
 #include <log/message_reader.h>
-#include <log/stringutil.h>
+#include <log/strutil.h>
 
 
 using namespace std;
 using namespace std;
 
 
@@ -26,87 +34,90 @@ namespace log {
 MessageReader::~MessageReader() {
 MessageReader::~MessageReader() {
 }
 }
 
 
-// Return error text
+// Get/Set the dictionary.
 
 
-string MessageReader::errorText(MessageReader::Status status) {
-    switch (status) {
-        case SUCCESS:
-            return "Success";
-
-        case DUPLPRFX:
-            return "Error, duplicate $PREFIX directive found";
+MessageDictionary* MessageReader::getDictionary() const {
+    return dictionary_;
+}
 
 
-        case PRFXEXTRARG:
-            return "Error, $PREFIX directive has extra arguments";
+void
+MessageReader::setDictionary(MessageDictionary* dictionary) {
+    dictionary_ = dictionary;
+    dictionary_->clearOverflow();
+}
 
 
-        case PRFXINVARG:
-            return "Error, $PREFIX directive has an invalid argument";
 
 
-        case PRFXNOARG:
-            return "Error, $PREFIX directive has no arguments";
+// Read the file.
 
 
-        case UNRECDIR:
-            return "Error, unrecognised directive";
+void
+MessageReader::readFile(const string& file, MessageReader::Mode mode) {
 
 
-        default:
-            return "Unknown message code";
+    // Open the file
+    ifstream infile(file.c_str());
+    if (infile.fail()) {
+        throw MessageException(MSG_OPENIN, file, strerror(errno));
     }
     }
-}
-
-// Read the file
 
 
-MessageReader::Status MessageReader::readFile(const string&) {
-    return OPENIN;
-}
-
-// Clear the Message Map
+    // Loop round reading it.
+    string line;
+    getline(infile, line);
+    while (infile.good()) {
+        processLine(line, mode);
+        getline(infile, line);
+    }
 
 
-void MessageReader::clear() {
+    // Why did the loop terminate?
+    if (! infile.eof()) {
+        throw MessageException(MSG_READERR, file, strerror(errno));
+    }
+    infile.close();
 }
 }
 
 
 // Parse a line of the file
 // Parse a line of the file
 
 
-MessageReader::Status MessageReader::processLine(const string& line) {
+void
+MessageReader::processLine(const string& line, MessageReader::Mode mode) {
 
 
     // Get rid of leading and trailing spaces
     // Get rid of leading and trailing spaces
-    string text = StringUtil::trim(line);
+    string text = isc::strutil::trim(line);
 
 
     if (text.empty()) {
     if (text.empty()) {
-        return SUCCESS;             // Ignore blank lines
+        ;                           // Ignore blank lines
 
 
     } else if ((text[0] == '#') || (text[0] == '+')) {
     } else if ((text[0] == '#') || (text[0] == '+')) {
-        return SUCCESS;             // Ignore comments or descriptions
+        ;                           // Ignore comments or descriptions
 
 
     } else if (text[0] == '$') {
     } else if (text[0] == '$') {
-        return directive(text);     // Process directives
+        parseDirective(text);       // Process directives
 
 
     } else {
     } else {
-        return OPENIN;
+        parseMessage(text, mode);   // Process other lines
 
 
     }
     }
 }
 }
 
 
 // Process directive
 // Process directive
 
 
-MessageReader::Status MessageReader::directive(const std::string& text) {
+void
+MessageReader::parseDirective(const std::string& text) {
 
 
     static string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
     static string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
 
 
     // Regardless of what happens, all prefixes will be uppercase (as will
     // Regardless of what happens, all prefixes will be uppercase (as will
     // all symbols).
     // all symbols).
     string line = text;
     string line = text;
-    StringUtil::uppercase(line);
-    vector<string> tokens = StringUtil::tokens(line);
+    isc::strutil::uppercase(line);
+    vector<string> tokens = isc::strutil::tokens(line);
 
 
     // Only $PREFIX is recognised so far, so we'll handle it here.
     // Only $PREFIX is recognised so far, so we'll handle it here.
     if (tokens[0] != string("$PREFIX")) {
     if (tokens[0] != string("$PREFIX")) {
-        return UNRECDIR;            // Directive is not prefix
+        throw MessageException(MSG_UNRECDIR, tokens[0]);
 
 
     } else if (tokens.size() < 2) {
     } else if (tokens.size() < 2) {
-        return PRFXNOARG;           // Does not have an argument
+        throw MessageException(MSG_PRFNOARG);
 
 
     } else if (tokens.size() > 2) {
     } else if (tokens.size() > 2) {
-        return PRFXEXTRARG;         // Too many arguments
+        throw MessageException(MSG_PRFEXTRARG);
 
 
     }
     }
 
 
@@ -117,20 +128,59 @@ MessageReader::Status MessageReader::directive(const std::string& text) {
     if ((tokens[1].find_first_not_of(valid) != string::npos) ||
     if ((tokens[1].find_first_not_of(valid) != string::npos) ||
         (std::isdigit(tokens[1][0]))) {
         (std::isdigit(tokens[1][0]))) {
 
 
-        // Invalid character in string opr it starts with a digit.
-        return PRFXINVARG;
+        // Invalid character in string or it starts with a digit.
+        throw MessageException(MSG_PRFINVARG, tokens[1]);
     }
     }
 
 
     // All OK - unless the prefix has already been set.
     // All OK - unless the prefix has already been set.
 
 
     if (prefix_.size() != 0) {
     if (prefix_.size() != 0) {
-        return DUPLPRFX;
+        throw MessageException(MSG_DUPLPRFX);
     }
     }
 
 
     // Prefix has not been set, so set it and return success.
     // Prefix has not been set, so set it and return success.
 
 
     prefix_ = tokens[1];
     prefix_ = tokens[1];
-    return SUCCESS;
+}
+
+// Process message.  By the time this method is called, the line has been
+// stripped of leading and trailing spaces, and we believe that it is a line
+// defining a message.  The first token on the line is convered to uppercase
+// and becomes the message ID; the rest of the line is the message text.
+
+void
+MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
+
+    static string delimiters("\t\n ");   // Delimiters
+
+    // Look for the first delimiter.
+    size_t first_delim = text.find_first_of(delimiters);
+    if (first_delim == string::npos) {
+
+        // Just a single token in the line - this is not valid
+        throw MessageException(MSG_ONETOKEN, text);
+    }
+
+    // Extract the first token into the message ID
+    MessageID ident = text.substr(0, first_delim);
+
+    // Locate the start of the message text
+    size_t first_text = text.find_first_not_of(delimiters, first_delim);
+    if (first_text == string::npos) {
+
+        // ?? This happens if there are trailing delimiters, which should not
+        // occur as we have stripped trailing spaces off the line.  Just treat
+        // this as a single-token error for simplicity's sake.
+        throw MessageException(MSG_ONETOKEN, text);
+    }
+
+    // Add the result to the dictionary.
+    if (mode == ADD) {
+        (void) dictionary_->add(ident, text.substr(first_text));
+    }
+    else {
+        (void) dictionary_->replace(ident, text.substr(first_text));
+    }
 }
 }
 
 
 } // namespace log
 } // namespace log

+ 63 - 59
src/lib/log/message_reader.h

@@ -22,6 +22,9 @@
 #include <string>
 #include <string>
 #include <vector>
 #include <vector>
 
 
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+
 namespace isc {
 namespace isc {
 namespace log {
 namespace log {
 
 
@@ -33,23 +36,18 @@ namespace log {
 class MessageReader {
 class MessageReader {
 public:
 public:
 
 
-    /// \brief Status Returns
+    /// \brief Read Mode
+    ///
+    /// If ADD, messages are added to the dictionary if the ID does not exist
+    /// there.  If it does, the ID is added to the dictionary's overflow
+    /// vector.
     ///
     ///
-    /// It may seem odd that a class devoted to reading logfile messages does
-    /// not have its own set of messages.  The reason is that this class is
-    /// used in both the server and in the message compiler.  The latter is
-    /// a stand-along program used to create message files, so at the time this
-    /// file is compiled, there is nothing to build an associated message file.
+    /// If REPLACE, the dictionary is only modified if the message ID already
+    /// exists in it.  New message IDs are added to the overflow vector.
     typedef enum {
     typedef enum {
-        SUCCESS,                // Success, all OK.
-        DUPLPRFX,               // Error, duplicate prefix found
-        OPENIN,                 // Error openinin input file
-        PRFXEXTRARG,            // Error, $PREFIX directive has extra arguments
-        PRFXINVARG,             // Error, $PREFIX has invalid argument
-        PRFXNOARG,              // Error, $PREFIX directive has no arguments
-        UNRECDIR                // Error, unrecognised directive
-
-    } Status;                   // Status code
+        ADD,
+        REPLACE
+    } Mode;
 
 
     /// \brief Other Types
     /// \brief Other Types
     typedef std::map<std::string, std::string>  MessageMap;
     typedef std::map<std::string, std::string>  MessageMap;
@@ -60,38 +58,48 @@ public:
     /// Default constructor.  All work is done in the main readFile code (so
     /// Default constructor.  All work is done in the main readFile code (so
     /// that a status return can be returned instead of needing to throw an
     /// that a status return can be returned instead of needing to throw an
     /// exception).
     /// exception).
-    MessageReader() : messages_(), duplicates_()
+    ///
+    /// \param dictionary Dictionary to which messages read read from the file
+    /// are added.  (This should be a local dictionary when the class is used in
+    /// the message compiler, and the global dictionary when used in a server.
+    /// The ownership of the dictionary object is not transferred - the caller
+    /// is responsible for managing the lifetime of the dictionary.
+    MessageReader(MessageDictionary* dictionary = NULL) :
+        dictionary_(dictionary)
     {}
     {}
 
 
+
     /// \brief Virtual Destructor
     /// \brief Virtual Destructor
     virtual ~MessageReader();
     virtual ~MessageReader();
 
 
-    /// \brief Return Error Text
+
+    /// \brief Get Dictionary
     ///
     ///
-    /// Returns the message associated with the error code
+    /// Returns the pointer to the dictionary object.  Note that ownership is
+    /// not transferred - the caller should not delete it.
+    ///
+    /// \return Pointer to current dictionary object
+    virtual MessageDictionary* getDictionary() const;
+
+
+    /// \brief Set Dictionary
     ///
     ///
-    /// \param status Status code for which a message is required
+    /// Sets the current dictionary object.
     ///
     ///
-    /// \return Text of the error.
-    virtual std::string errorText(Status status);
+    /// \param dictionary New dictionary object. The ownership of the dictionary
+    /// object is not transferred - the caller is responsible for managing the
+    /// lifetime of the dictionary.
+    virtual void setDictionary(MessageDictionary* dictionary);
 
 
 
 
     /// \brief Read File
     /// \brief Read File
     ///
     ///
     /// This is the main method of the class and reads in the file, parses it,
     /// This is the main method of the class and reads in the file, parses it,
-    /// and stores the result in the message map.
+    /// and stores the result in the message dictionary.
     ///
     ///
     /// \param file Name of the message file.
     /// \param file Name of the message file.
-    ///
-    /// \return Status return.  Should be SUCCESS, else an error has occurred.
-    virtual Status readFile(const std::string& file);
-
-
-    /// \brief Clears Message Mapgit find new files
-    ///
-    /// In the event of an instance of the class needing to be reused, this
-    /// method will clear the message map and the list of duplicated.
-    virtual void clear();
+    /// \param mode Addition mode.  See the description of the "Mode" enum.
+    virtual void readFile(const std::string& file, Mode mode = ADD);
 
 
 
 
     /// \brief Process Line
     /// \brief Process Line
@@ -101,30 +109,10 @@ public:
     /// to the message map.
     /// to the message map.
     ///
     ///
     /// \param line Line of text to process
     /// \param line Line of text to process
-    ///
-    /// \return Status return
-    virtual Status processLine(const std::string& line);
+    /// \param mode If a message line, how to add the message to the dictionary.
+    virtual void processLine(const std::string& line, Mode mode = ADD);
 
 
 
 
-    /// \brief Return Message Map
-    ///
-    /// Returns the message map.
-    ///
-    /// \return Returns a copy of the internal map.
-    /// TODO: Usse a reference?
-    virtual MessageMap getMessageMap() const {
-        return messages_;
-    }
-
-    /// \brief Return Message Duplicates
-    ///
-    /// Returns a copy of the duplicates vector.
-    ///
-    /// \return Copy of the duplicates vector
-    virtual MessageDuplicates getMessageDuplicates() {
-        return duplicates_;
-    }
-
     /// \brief Get Prefix
     /// \brief Get Prefix
     ///
     ///
     /// \return Argument to the $PREFIX directive (if present)
     /// \return Argument to the $PREFIX directive (if present)
@@ -132,21 +120,37 @@ public:
         return prefix_;
         return prefix_;
     }
     }
 
 
+
+    /// \brief Clear Prefix
+    ///
+    /// Clears the current prefix.
+    virtual void clearPrefix() {
+        prefix_ = "";
+    }
+
 private:
 private:
 
 
+    /// \brief Handle a Message Definition
+    ///
+    /// Passed a line that should contain a message, this processes that line
+    /// and adds it to the dictionary according to the mode setting.
+    ///
+    /// \param line Line of text
+    /// \param ADD or REPLACE depending on how the reader is operating.  (See
+    /// the description of the Mode typedef for details.)
+    void parseMessage(const std::string& line, Mode mode);
+
+
     /// \brief Handle Directive
     /// \brief Handle Directive
     ///
     ///
     /// Passed a line starting with a "$", this handles the processing of
     /// Passed a line starting with a "$", this handles the processing of
     /// directives.
     /// directives.
     ///
     ///
     /// \param line Line of text that starts with "$",
     /// \param line Line of text that starts with "$",
-    ///
-    /// \return Status return code.  NORMAL implies success
-    Status directive(const std::string& line);
+    void parseDirective(const std::string& line);
 
 
     /// Attributes
     /// Attributes
-    MessageMap          messages_;      // Message map
-    MessageDuplicates   duplicates_;    // Duplicate messages
+    MessageDictionary*  dictionary_;    // Dictionary to add messages to
     std::string         prefix_;        // Input of $PREFIX statement
     std::string         prefix_;        // Input of $PREFIX statement
 };
 };
 
 

+ 9 - 27
src/lib/log/log.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -14,37 +14,19 @@
 
 
 // $Id$
 // $Id$
 
 
-#ifndef __LOG_H
-#define __LOG_H
+#ifndef __MESSAGE_TYPES_H
+#define __MESSAGE_TYPES_H
+
+#include <string>
 
 
 namespace isc {
 namespace isc {
 namespace log {
 namespace log {
 
 
-class RootLogger : public Logger
-{
-public:
-
-    /// \brief Return Root Logger
-    ///
-    /// Returns the root logger for the system.  Only one root logger is
-    /// defined and the name corresponds to the 
-    static Log* getRootLogger() {
-        static Log root_logger();
-
-        return &root_logger;
-    }
-
-    /// \brief Set Logger Name
-    ///
-    /// Sets the name of the root logger.  This is dynamic, and 
-
-private:
-    static std::string  root_name_;
-};
+typedef std::string MessageID;
 
 
-}
-}
+} // namespace log
+} // namespace isc
 
 
 
 
 
 
-#endif // __LOG_H
+#endif // __MESSAGE_TYPES_H

+ 26 - 0
src/lib/log/messagedef.cc

@@ -0,0 +1,26 @@
+// File created from messagedef.mes on Fri Jan  7 17:26:42 2011
+
+#include <cstddef>
+#include <log/message_initializer.h>
+
+using namespace isc::log;
+
+namespace {
+
+const char* values[] = {
+    "DUPLPRFX", "duplicate $PREFIX directive found",
+    "ONETOKEN", "a line containing a message ID ('%s') and nothing else was found",
+    "OPENIN", "unable to open %s for input: %s",
+    "OPENOUT", "unable to open %s for output: %s",
+    "PRFEXTRARG", "$PREFIX directive has too many arguments",
+    "PRFINVARG", "$PREFIX directive has an invalid argument ('%s')",
+    "PRFNOARG", "no arguments were given to the $PREFIX directive",
+    "READERR", "error reading from %s: %s",
+    "UNRECDIR", "unrecognised directive '%s'",
+    "WRITERR", "error writing to %s: %s",
+    NULL
+};
+
+} // Anonymous namespace
+
+MessageInitializer messagedef_cc_Fri_Jan__7_17_26_42_2011(values);

+ 23 - 0
src/lib/log/messagedef.h

@@ -0,0 +1,23 @@
+// File created from messagedef.mes on Fri Jan  7 17:26:42 2011
+
+#ifndef __MESSAGEDEF_H
+#define __MESSAGEDEF_H
+
+#include <log/message_types.h>
+
+namespace {
+
+isc::log::MessageID MSG_DUPLPRFX = "DUPLPRFX";
+isc::log::MessageID MSG_ONETOKEN = "ONETOKEN";
+isc::log::MessageID MSG_OPENIN = "OPENIN";
+isc::log::MessageID MSG_OPENOUT = "OPENOUT";
+isc::log::MessageID MSG_PRFEXTRARG = "PRFEXTRARG";
+isc::log::MessageID MSG_PRFINVARG = "PRFINVARG";
+isc::log::MessageID MSG_PRFNOARG = "PRFNOARG";
+isc::log::MessageID MSG_READERR = "READERR";
+isc::log::MessageID MSG_UNRECDIR = "UNRECDIR";
+isc::log::MessageID MSG_WRITERR = "WRITERR";
+
+} // Anonymous namespace
+
+#endif // __MESSAGEDEF_H

+ 71 - 0
src/lib/log/messagedef.mes

@@ -0,0 +1,71 @@
+# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# $Id$
+
+$PREFIX MSG_
+
+# \brief Message Utility Message File
+#
+# This is the source of the set of messages generated by the message and logging
+# components.  The associated .h and .cc files are created by hand from this
+# file though and are not built during the build process; this is to avoid the
+# chicken-and-egg situation where we need the files to build the message
+# compiler, yet we need the compiler to build the files.
+
+DUPLPRFX    duplicate $PREFIX directive found
++ When reading a message file, more than one $PREFIX directive was found.  In
++ this version of the code, such a condition is regarded as an error and the
++ read will be abandonded.
+
+ONETOKEN    a line containing a message ID ('%s') and nothing else was found
++ Message definitions comprise lines starting with a message identification (a
++ symbolic name for the message) and followed by the text of the message.  This
++ error is generated when a line is found in the message file that contains just
++ the message identification.
+
+OPENIN      unable to open %s for input: %s
++ The program was not able to open the specified input file for the reason
++ given.
+
+OPENOUT     unable to open %s for output: %s
++ The program was not able to open the specified output file for the reason
++ given.
+
+PRFEXTRARG  $PREFIX directive has too many arguments
++ The $PREFIX directive takes a single argument, a prefix to be added to the
++ symbol names when a C++ .h file is created.  This error is generated when the
++ compiler finds a $PREFIX directive with more than one argument.
+
+PRFINVARG   $PREFIX directive has an invalid argument ('%s')
++ The $PREFIX argument is used in a symbol name in a C++ header file.  As such,
++ it must adhere to restrictions on C++ symbol names (e.g. may only contain
++ alphanumeric characters or underscores, and may nor start with a digit).  A
++ $PREFIX directive was found with an argument (given in the message) that
++ violates those restictions.
+
+PRFNOARG    no arguments were given to the $PREFIX directive
++ The $PREFIX directive takes a single argument, a prefix to be added to the
++ symbol names when a C++ .h file is created.  This error is generated when the
++ compiler finds a $PREFIX directive with noa rguments.
+
+READERR     error reading from %s: %s
++ The specified error was encountered reading from the named input file.
+
+UNRECDIR    unrecognised directive '%s'
++ A line starting with a dollar symbol was found, but the first word on the line
++ (shown in the message) was not a recognised message compiler directive.
+
+WRITERR     error writing to %s: %s
++ The specified error was encountered writing to the named output file.

+ 0 - 129
src/lib/log/stringutil.h

@@ -1,129 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-// $Id$
-
-#ifndef __STRINGUTIL_H
-#define __STRINGUTIL_H
-
-#include <algorithm>
-#include <cctype>
-#include <string>
-#include <vector>
-
-namespace isc {
-namespace log {
-
-/// \brief A Set of C++ Utilities for Manipulating Strings
-
-class StringUtil {
-public:
-    /// \brief Normalize Backslash
-    ///
-    /// Only active on Windows, this replaces all "\" in a string with "/"
-    /// and returns the result.  On other systems it is a no-op.  Note that
-    /// Windows does recognise file names with the "\" replaced by "/" (at
-    /// least in system calls, if not the command line).
-    ///
-    /// \param name Name to be substituted
-    static void normalizeSlash(std::string& name) {
-        if (! name.empty()) {
-            size_t pos = 0;
-            while ((pos = name.find('\\', pos)) != std::string::npos) {
-                name[pos] = '/';
-            }
-        }
-    }
-
-    /// \brief Trim Leading and Trailing Spaces
-    ///
-    /// Returns a copy of the input string but with any leading or trailing
-    /// spaces or tabs removed.
-    ///
-    /// \param instring Input string to modify
-    ///
-    /// \return String with leading and trailing spaces removed
-    static std::string trim(const std::string& instring);
-
-    /// \brief Split String into Tokens
-    ///
-    /// Splits a string into tokens (the tokens being delimited by one or more
-    /// of the delimiter characters) and returns the tokens in a vector array.
-    /// Note that adjacent delimiters are considered to be a single delimiter.
-    ///
-    /// We could use Boost for this, but this (simple) function eliminates one
-    /// dependency in the code.
-    ///
-    /// \param text String to be split.  Passed by value as the internal copy
-    /// is altered during the processing.
-    /// \param delim Delimiter characters
-    ///
-    /// \return Vector of tokens.
-    static std::vector<std::string> tokens(const std::string text,
-            const std::string& delim = std::string(" \t\n"));
-
-    /// \brief Uppercase Character
-    ///
-    /// Needed to pass as an argument to transform() in uppercase(), as the
-    /// function std::toupper() takes an "int" as its argument and the template
-    /// expansion mechanism gets confused.
-    ///
-    /// \param chr Character to be upper-cased.
-    ///
-    /// \return Uppercase version of the argument
-    static char toUpper(char chr) {
-        return static_cast<char>(std::toupper(static_cast<int>(chr)));
-    }
-
-    /// \brief Uppercase String
-    ///
-    /// A convenience function to uppercase a string
-    ///
-    /// \param text String to be upper-cased.
-    static void uppercase(std::string& text) {
-        std::transform(text.begin(), text.end(), text.begin(),
-            StringUtil::toUpper);
-    }
-
-    /// \brief Lowercase Character
-    ///
-    /// Needed to pass as an argument to transform() in lowercase(), as the
-    /// function std::tolower() takes an "int" as its argument and the template
-    /// expansion mechanism gets confused.
-    ///
-    /// \param chr Character to be lower-cased.
-    ///
-    /// \return Lowercase version of the argument
-    static char toLower(char chr) {
-        return static_cast<char>(std::tolower(static_cast<int>(chr)));
-    }
-
-    /// \brief Lowercase String
-    ///
-    /// A convenience function to lowercase a string
-    ///
-    /// \param text String to be lower-cased.
-    static void lowercase(std::string& text) {
-        std::transform(text.begin(), text.end(), text.begin(),
-            StringUtil::toLower);
-    }
-
-};
-
-} // namespace log
-} // namespace isc
-
-
-
-#endif // __STRINGUTIL_H

+ 59 - 6
src/lib/log/stringutil.cc

@@ -14,19 +14,31 @@
 
 
 // $Id$
 // $Id$
 
 
+#include <numeric>
 #include <iostream>
 #include <iostream>
 
 
 #include <string.h>
 #include <string.h>
-#include <stringutil.h>
+#include <strutil.h>
 
 
 using namespace std;
 using namespace std;
 
 
 namespace isc {
 namespace isc {
-namespace log {
+namespace strutil {
+
+// Normalize slashes
+
+void normalizeSlash(std::string& name) {
+    if (! name.empty()) {
+        size_t pos = 0;
+        while ((pos = name.find('\\', pos)) != std::string::npos) {
+            name[pos] = '/';
+        }
+    }
+}
 
 
 // Trim String
 // Trim String
 
 
-string StringUtil::trim(const string& instring) {
+string trim(const string& instring) {
     static const char* blanks = " \t\n";
     static const char* blanks = " \t\n";
 
 
     string retstring = "";
     string retstring = "";
@@ -50,9 +62,8 @@ string StringUtil::trim(const string& instring) {
 // Tokenise string.  As noted in the header, this is locally written to avoid
 // Tokenise string.  As noted in the header, this is locally written to avoid
 // another dependency on a Boost library.
 // another dependency on a Boost library.
 
 
-vector<string> StringUtil::tokens(const std::string text,
-    const std::string& delim)
-{
+vector<string>
+tokens(const std::string text, const std::string& delim) {
     vector<string> result;
     vector<string> result;
 
 
     // Search for the first non-delimiter character
     // Search for the first non-delimiter character
@@ -79,5 +90,47 @@ vector<string> StringUtil::tokens(const std::string text,
     return result;
     return result;
 }
 }
 
 
+// Local function to pass to accumulate() for summing up string lengths.
+
+namespace {
+
+size_t
+lengthSum(string::size_type curlen, const string& cur_string) {
+    return (curlen + cur_string.size());
+}
+
+}
+
+// Provide printf-style formatting.
+
+std::string
+format(const std::string& format, const std::vector<std::string>& args) {
+
+    static const string flag = "%s";
+
+    // Initialize return string.  To speed things up, we'll reserve an
+    // appropriate amount of space - current string size, plus length of all
+    // the argument strings, less two characters for each argument (the %s in
+    // the format string is being replaced).
+    string result;
+    size_t length = accumulate(args.begin(), args.end(), format.size(),
+        lengthSum) - (args.size() * flag.size());
+    result.reserve(length);
+
+    // Iterate through replacing all tokens
+    result = format;
+    size_t tokenpos = 0;    // Position of last token replaced
+    int i = 0;              // Index into argument array
+
+    while ((i < args.size()) && (tokenpos != string::npos)) {
+        tokenpos = result.find(flag, tokenpos);
+        if (tokenpos != string::npos) {
+            result.replace(tokenpos, flag.size(), args[i++]);
+        }
+    }
+
+    return result;
+}
+
 } // namespace log
 } // namespace log
 } // namespace isc
 } // namespace isc

+ 135 - 0
src/lib/log/strutil.h

@@ -0,0 +1,135 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __STRUTIL_H
+#define __STRUTIL_H
+
+#include <algorithm>
+#include <cctype>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace strutil {
+
+/// \brief A Set of C++ Utilities for Manipulating Strings
+
+/// \brief Normalize Backslash
+///
+/// Only relevant to Windows, this replaces all "\" in a string with "/" and
+/// returns the result.  On other systems it is a no-op.  Note that Windows does
+/// recognise file names with the "\" replaced by "/" (at least in system calls,
+/// if not the command line).
+///
+/// \param name Name to be substituted
+void normalizeSlash(std::string& name);
+
+
+/// \brief Trim Leading and Trailing Spaces
+///
+/// Returns a copy of the input string but with any leading or trailing spaces
+/// or tabs removed.
+///
+/// \param instring Input string to modify
+///
+/// \return String with leading and trailing spaces removed
+std::string trim(const std::string& instring);
+
+
+/// \brief Split String into Tokens
+///
+/// Splits a string into tokens (the tokens being delimited by one or more of
+/// the delimiter characters) and returns the tokens in a vector array. Note
+/// that adjacent delimiters are considered to be a single delimiter.
+///
+/// We could use Boost for this, but this (simple) function eliminates one
+/// dependency in the code.
+///
+/// \param text String to be split.  Passed by value as the internal copy is
+/// altered during the processing.
+/// \param delim Delimiter characters
+///
+/// \return Vector of tokens.
+std::vector<std::string> tokens(const std::string text,
+        const std::string& delim = std::string(" \t\n"));
+
+
+/// \brief Uppercase Character
+///
+/// Needed to pass as an argument to transform() in uppercase(), as the
+/// function std::toupper() takes an "int" as its argument and the template
+/// expansion mechanism gets confused.
+///
+/// \param chr Character to be upper-cased.
+///
+/// \return Uppercase version of the argument
+inline char toUpper(char chr) {
+    return static_cast<char>(std::toupper(static_cast<int>(chr)));
+}
+
+
+/// \brief Uppercase String
+///
+/// A convenience function to uppercase a string.
+///
+/// \param text String to be upper-cased.
+inline void uppercase(std::string& text) {
+    std::transform(text.begin(), text.end(), text.begin(),
+        isc::strutil::toUpper);
+}
+
+/// \brief Lowercase Character
+///
+/// Needed to pass as an argument to transform() in lowercase(), as the
+/// function std::tolower() takes an "int" as its argument and the template
+/// expansion mechanism gets confused.
+///
+/// \param chr Character to be lower-cased.
+///
+/// \return Lowercase version of the argument
+inline char toLower(char chr) {
+    return static_cast<char>(std::tolower(static_cast<int>(chr)));
+}
+
+/// \brief Lowercase String
+///
+/// A convenience function to lowercase a string
+///
+/// \param text String to be lower-cased.
+inline void lowercase(std::string& text) {
+    std::transform(text.begin(), text.end(), text.begin(),
+        isc::strutil::toLower);
+}
+
+
+/// \brief Apply Formatting
+///
+/// Given a printf-style format string containing only "%s" place holders
+/// (others are ignored) and a vector of strings, this produces a single string
+/// with the placeholders replaced.
+///
+/// \param format Format string
+/// \param args Vector of argument strings
+///
+/// \return Resultant string
+std::string format(const std::string& format,
+    const std::vector<std::string>& args);
+
+
+} // namespace strutil
+} // namespace isc
+
+#endif // __STRUTIL_H

+ 3 - 1
src/lib/log/tests/Makefile.am

@@ -17,8 +17,10 @@ TESTS += run_unittests
 run_unittests_SOURCES  = root_logger_name_unittest.cc
 run_unittests_SOURCES  = root_logger_name_unittest.cc
 run_unittests_SOURCES += filename_unittest.cc
 run_unittests_SOURCES += filename_unittest.cc
 run_unittests_SOURCES += logger_unittest.cc
 run_unittests_SOURCES += logger_unittest.cc
+run_unittests_SOURCES += message_dictionary_unittest.cc
 run_unittests_SOURCES += message_reader_unittest.cc
 run_unittests_SOURCES += message_reader_unittest.cc
-run_unittests_SOURCES += stringutil_unittest.cc
+run_unittests_SOURCES += message_initializer_unittest.cc
+run_unittests_SOURCES += strutil_unittest.cc
 run_unittests_SOURCES += xdebuglevel_unittest.cc
 run_unittests_SOURCES += xdebuglevel_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

+ 195 - 0
src/lib/log/tests/message_dictionary_unittest.cc

@@ -0,0 +1,195 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
+
+#include <cstddef>
+#include <string>
+#include <gtest/gtest.h>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+class MessageDictionaryTest : public ::testing::Test {
+protected:
+    MessageDictionaryTest() : 
+        alpha_id("ALPHA"), alpha_text("This is alpha"),
+        beta_id("BETA"), beta_text("This is beta"),
+        gamma_id("GAMMA"), gamma_text("This is gamma")
+    {
+    }
+
+    MessageID alpha_id;
+    std::string alpha_text;
+    MessageID beta_id;
+    std::string beta_text;
+    MessageID gamma_id;
+    std::string gamma_text;
+
+};
+
+
+// Check that the global dictionary is a singleton.
+
+TEST_F(MessageDictionaryTest, GlobalTest) {
+    MessageDictionary* global = MessageDictionary::globalDictionary();
+    EXPECT_FALSE(NULL == global);
+
+    MessageDictionary* global2 = MessageDictionary::globalDictionary();
+    EXPECT_EQ(global2, global);
+}
+
+// Check that adding messages works
+
+TEST_F(MessageDictionaryTest, Add) {
+    MessageDictionary dictionary;
+    EXPECT_EQ(0, dictionary.size());
+
+    vector<MessageID> overflow = dictionary.getOverflow();
+    EXPECT_EQ(0, overflow.size());
+
+    // Add a few messages and check that we can look them up and that there is
+    // nothing in the overflow vector.
+    EXPECT_TRUE(dictionary.add(alpha_id, alpha_text));
+    EXPECT_TRUE(dictionary.add(beta_id, beta_text));
+    EXPECT_EQ(2, dictionary.size());
+
+    overflow = dictionary.getOverflow();
+    EXPECT_EQ(0, overflow.size());
+
+    EXPECT_EQ(alpha_text, dictionary.getText(alpha_id));
+    EXPECT_EQ(beta_text, dictionary.getText(beta_id));
+    EXPECT_EQ(string(""), dictionary.getText(gamma_id));
+
+    // Try adding a duplicate with different text.  It should not replace the
+    // current text and the ID should be in the overflow section.
+    EXPECT_FALSE(dictionary.add(alpha_id, gamma_text));
+    EXPECT_EQ(2, dictionary.size());
+
+    overflow = dictionary.getOverflow();
+    ASSERT_EQ(1, overflow.size());
+    EXPECT_EQ(alpha_id, overflow[0]);
+    EXPECT_EQ(alpha_text, dictionary.getText(alpha_id));
+}
+
+// Check that clearing the overflow vector works
+
+TEST_F(MessageDictionaryTest, ClearOverflow) {
+    MessageDictionary dictionary;
+    EXPECT_EQ(0, dictionary.size());
+
+    vector<MessageID> overflow = dictionary.getOverflow();
+
+    // Add one message twice to get an overflow
+    EXPECT_TRUE(dictionary.add(alpha_id, alpha_text));
+    EXPECT_FALSE(dictionary.add(alpha_id, alpha_text));
+    EXPECT_EQ(1, dictionary.size());
+
+    // Check the overflow
+    overflow = dictionary.getOverflow();
+    ASSERT_EQ(1, overflow.size());
+    EXPECT_EQ(alpha_id, overflow[0]);
+
+    // ... and check that clearing it works
+    dictionary.clearOverflow();
+    overflow = dictionary.getOverflow();
+    ASSERT_EQ(0, overflow.size());
+}
+
+// Check that replacing messages works.
+
+TEST_F(MessageDictionaryTest, Replace) {
+    MessageDictionary dictionary;
+    EXPECT_EQ(0, dictionary.size());
+
+    vector<MessageID> overflow = dictionary.getOverflow();
+    EXPECT_EQ(0, overflow.size());
+
+    // Try to replace a non-existent message
+    EXPECT_FALSE(dictionary.replace(alpha_id, alpha_text));
+    EXPECT_EQ(0, dictionary.size());
+    overflow = dictionary.getOverflow();
+    ASSERT_EQ(1, overflow.size());
+    EXPECT_EQ(alpha_id, overflow[0]);
+
+    // Clear the overflow and add a couple of messages.
+    dictionary.clearOverflow();
+    overflow = dictionary.getOverflow();
+    ASSERT_EQ(0, overflow.size());
+
+    EXPECT_TRUE(dictionary.add(alpha_id, alpha_text));
+    EXPECT_TRUE(dictionary.add(beta_id, beta_text));
+    EXPECT_EQ(2, dictionary.size());
+
+    // Replace an existing message
+    EXPECT_TRUE(dictionary.replace(alpha_id, gamma_text));
+    EXPECT_EQ(2, dictionary.size());
+    EXPECT_EQ(gamma_text, dictionary.getText(alpha_id));
+    overflow = dictionary.getOverflow();
+    ASSERT_EQ(0, overflow.size());
+
+    // ... and replace non-existent message (but now the dictionary has some
+    // items in it).
+    EXPECT_FALSE(dictionary.replace(gamma_id, alpha_text));
+    EXPECT_EQ(2, dictionary.size());
+    EXPECT_EQ(string(""), dictionary.getText(gamma_id));
+}
+
+// Load test
+
+TEST_F(MessageDictionaryTest, LoadTest) {
+    static const char* data1[] = {
+        "ALPHA", "This is alpha",
+        "BETA", "This is beta",
+        "GAMMA", "This is gamma",
+        NULL
+    };
+
+    static const char* data2[] = {
+        "DELTA", "This is delta",
+        "EPSILON", "This is epsilon",
+        "ETA", NULL
+    };
+
+    MessageDictionary dictionary1;
+    EXPECT_EQ(0, dictionary1.size());
+
+    // Load a dictionary1.
+    dictionary1.load(data1);
+    EXPECT_EQ(3, dictionary1.size());
+    EXPECT_EQ(string(data1[1]), dictionary1.getText(data1[0]));
+    EXPECT_EQ(string(data1[3]), dictionary1.getText(data1[2]));
+    EXPECT_EQ(string(data1[5]), dictionary1.getText(data1[4]));
+    vector<MessageID> overflow = dictionary1.getOverflow();
+    EXPECT_EQ(0, overflow.size());
+
+    // Attempt an overwrite
+    dictionary1.load(data1);
+    EXPECT_EQ(3, dictionary1.size());
+    overflow = dictionary1.getOverflow();
+    EXPECT_EQ(3, overflow.size());
+
+    // Try a new dictionary but with an incorrect number of elements
+    MessageDictionary dictionary2;
+    EXPECT_EQ(0, dictionary2.size());
+
+    dictionary2.load(data2);
+    EXPECT_EQ(2, dictionary2.size());
+    EXPECT_EQ(string(data2[1]), dictionary2.getText(data2[0]));
+    EXPECT_EQ(string(data2[3]), dictionary2.getText(data2[2]));
+    EXPECT_EQ(string(""), dictionary2.getText(data2[4]));
+}

+ 59 - 0
src/lib/log/tests/message_initializer_unittest.cc

@@ -0,0 +1,59 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
+
+#include <cstddef>
+#include <string>
+#include <gtest/gtest.h>
+#include <log/message_dictionary.h>
+#include <log/message_initializer.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+// Declare a set of messages to go into the global dictionary.
+
+namespace {
+const char* values[] = {
+    "GLOBAL1", "global message one",
+    "GLOBAL2", "global message two",
+    NULL
+};
+}
+
+// Statically initialize the global dictionary with those messages.
+MessageInitializer init_message_initializer_unittest(values);
+
+
+
+class MessageInitializerTest : public ::testing::Test {
+protected:
+    MessageInitializerTest()
+    {
+    }
+};
+
+
+// Check that the global dictionary is initialized with the specified
+// messages.
+
+TEST_F(MessageInitializerTest, MessageTest) {
+    MessageDictionary* global = MessageDictionary::globalDictionary();
+
+    EXPECT_EQ(string("global message one"), global->getText("GLOBAL1"));
+    EXPECT_EQ(string("global message two"), global->getText("GLOBAL2"));
+    EXPECT_EQ(string(""), global->getText(""));
+}

+ 164 - 50
src/lib/log/tests/message_reader_unittest.cc

@@ -14,8 +14,13 @@
 
 
 // $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
 // $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
 
 
+#include <algorithm>
 #include <string>
 #include <string>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
+
+#include <log/messagedef.h>
+#include <log/message_dictionary.h>
+#include <log/message_exception.h>
 #include <log/message_reader.h>
 #include <log/message_reader.h>
 
 
 using namespace isc;
 using namespace isc;
@@ -24,64 +29,75 @@ using namespace std;
 
 
 class MessageReaderTest : public ::testing::Test {
 class MessageReaderTest : public ::testing::Test {
 protected:
 protected:
-    MessageReaderTest() : reader_()
+    MessageReaderTest() : dictionary_(), reader_()
     {
     {
+        dictionary_ = new MessageDictionary();
+        reader_.setDictionary(dictionary_);
+    }
+
+    ~MessageReaderTest() {
+        delete dictionary_;
     }
     }
 
 
-    MessageReader   reader_;        // Default reader object
+    MessageDictionary*  dictionary_;    // Dictionary to add messages to
+    MessageReader       reader_;        // Default reader object
 };
 };
 
 
 
 
-// Check for a couple of status messages
+// Check the get/set dictionary calls (using a local reader and dictionary).
 
 
-TEST_F(MessageReaderTest, StatusMessage) {
-    string unknown("Unknown message code");     // Default unknown message
+TEST_F(MessageReaderTest, GetSetDictionary) {
+    MessageReader reader;
+    EXPECT_TRUE(reader.getDictionary() == NULL);
 
 
-    EXPECT_NE(unknown, reader_.errorText(MessageReader::SUCCESS));
-    EXPECT_NE(unknown, reader_.errorText(MessageReader::DUPLPRFX));
-    EXPECT_NE(unknown, reader_.errorText(MessageReader::PRFXEXTRARG));
-    EXPECT_NE(unknown, reader_.errorText(MessageReader::PRFXINVARG));
-    EXPECT_NE(unknown, reader_.errorText(MessageReader::PRFXNOARG));
-    EXPECT_NE(unknown, reader_.errorText(MessageReader::UNRECDIR));
+    MessageDictionary dictionary;
+    reader.setDictionary(&dictionary);
+    EXPECT_EQ(&dictionary, reader.getDictionary());
 }
 }
 
 
-// Check for parsing blank lines and comments.  These should not add to the map and
-// each parse should return success.
+// Check for parsing blank lines and comments.  These should not add to the
+// dictionary and each parse should return success.
 
 
 TEST_F(MessageReaderTest, BlanksAndComments) {
 TEST_F(MessageReaderTest, BlanksAndComments) {
 
 
-    // Ensure that the map is empty
-    MessageReader::MessageMap mmap = reader_.getMessageMap();
-    EXPECT_EQ(0, mmap.size());
-
-    MessageReader::MessageDuplicates mduplicates = reader_.getMessageDuplicates();
-    EXPECT_EQ(0, mduplicates.size());
-
-    // Add a number of blank lines and comments and check that (a) they are parsed
-    // successfully ...
-    MessageReader::Status status = reader_.processLine("");
-    EXPECT_EQ(MessageReader::SUCCESS, status);
-    status = reader_.processLine(" ");
-    EXPECT_EQ(MessageReader::SUCCESS, status);
-    status = reader_.processLine(" \n ");
-    EXPECT_EQ(MessageReader::SUCCESS, status);
-    status = reader_.processLine("# This is a comment");
-    EXPECT_EQ(MessageReader::SUCCESS, status);
-    status = reader_.processLine("\t\t # Another comment");
-    EXPECT_EQ(MessageReader::SUCCESS, status);
-    status = reader_.processLine("  + A description line");
-    EXPECT_EQ(MessageReader::SUCCESS, status);
-    status = reader_.processLine("#+ A comment");
-    EXPECT_EQ(MessageReader::SUCCESS, status);
-    status = reader_.processLine("  +# A description line");
-    EXPECT_EQ(MessageReader::SUCCESS, status);
+    // Ensure that the dictionary is empty.
+    EXPECT_EQ(0, dictionary_->size());
+    vector<MessageID> overflow = dictionary_->getOverflow();
+    EXPECT_EQ(0, overflow.size());
+
+    // Add a number of blank lines and comments and check that (a) they are
+    // parsed successfully ...
+    EXPECT_NO_THROW(reader_.processLine(""));
+    EXPECT_NO_THROW(reader_.processLine(" "));
+    EXPECT_NO_THROW(reader_.processLine(" \n "));
+    EXPECT_NO_THROW(reader_.processLine("# This is a comment"));
+    EXPECT_NO_THROW(reader_.processLine("\t\t # Another comment"));
+    EXPECT_NO_THROW(reader_.processLine("  + A description line"));
+    EXPECT_NO_THROW(reader_.processLine("#+ A comment"));
+    EXPECT_NO_THROW(reader_.processLine("  +# A description line"));
 
 
     // ... and (b) nothing gets added to either the map or the duplicates
     // ... and (b) nothing gets added to either the map or the duplicates
-    mmap = reader_.getMessageMap();
-    EXPECT_EQ(0, mmap.size());
+    EXPECT_EQ(0, dictionary_->size());
+    overflow = dictionary_->getOverflow();
+    EXPECT_EQ(0, overflow.size());
+}
+
 
 
-    mduplicates = reader_.getMessageDuplicates();
-    EXPECT_EQ(0, mduplicates.size());
+// Local test to check that processLine generates the right exception.
+
+void
+processLineException(MessageReader& reader, const char* what,
+    MessageID& expected) {
+
+    try {
+        reader.processLine(what);
+        FAIL() << "MessageReader::processLine() should throw an exception " <<
+            " with message ID " << expected << " for '" << what << "'\n";
+    } catch (MessageException& e) {
+        EXPECT_EQ(expected, e.id());
+    } catch (...) {
+        FAIL() << "Unknown exception thrown by MessageReader::processLine()\n";
+    }
 }
 }
 
 
 // Check that it can parse a prefix
 // Check that it can parse a prefix
@@ -92,25 +108,123 @@ TEST_F(MessageReaderTest, Prefix) {
     EXPECT_EQ(string(""), reader_.getPrefix());
     EXPECT_EQ(string(""), reader_.getPrefix());
 
 
     // Check that a prefix directive with no argument generates an error.
     // Check that a prefix directive with no argument generates an error.
-    EXPECT_EQ(MessageReader::PRFXNOARG, reader_.processLine("$PREFIX"));
+    processLineException(reader_, "$PREFIX", MSG_PRFNOARG);
 
 
     // Check a prefix with multiple arguments is invalid
     // Check a prefix with multiple arguments is invalid
-    EXPECT_EQ(MessageReader::PRFXEXTRARG, reader_.processLine("$prefix A B"));
+    processLineException(reader_, "$prefix A B", MSG_PRFEXTRARG);
 
 
     // Prefixes should be alphanumeric (with underscores) and not start
     // Prefixes should be alphanumeric (with underscores) and not start
     // with a number.
     // with a number.
-    EXPECT_EQ(MessageReader::PRFXINVARG, reader_.processLine("$prefix ab[cd"));
-    EXPECT_EQ(MessageReader::PRFXINVARG, reader_.processLine("$prefix 123"));
-    EXPECT_EQ(MessageReader::PRFXINVARG, reader_.processLine("$prefix 1ABC"));
+    processLineException(reader_, "$prefix ab[cd", MSG_PRFINVARG);
+    processLineException(reader_, "$prefix 123", MSG_PRFINVARG);
+    processLineException(reader_, "$prefix 1ABC", MSG_PRFINVARG);
 
 
     // A valid prefix should be accepted
     // A valid prefix should be accepted
-    EXPECT_EQ(MessageReader::SUCCESS, reader_.processLine("$PREFIX   dlm__"));
+    EXPECT_NO_THROW(reader_.processLine("$PREFIX   dlm__"));
     EXPECT_EQ(string("DLM__"), reader_.getPrefix());
     EXPECT_EQ(string("DLM__"), reader_.getPrefix());
 
 
     // And check that the parser fails on invalid prefixes...
     // And check that the parser fails on invalid prefixes...
-    EXPECT_EQ(MessageReader::PRFXINVARG, reader_.processLine("$prefix 1ABC"));
+    processLineException(reader_, "$prefix 1ABC", MSG_PRFINVARG);
 
 
     // ... and rejects another valid one
     // ... and rejects another valid one
-    EXPECT_EQ(MessageReader::DUPLPRFX, reader_.processLine("$PREFIX ABC"));
+    processLineException(reader_, "$PREFIX ABC", MSG_DUPLPRFX);
+
+    // Check that we can clear the prefix as well
+    reader_.clearPrefix();
+    EXPECT_EQ(string(""), reader_.getPrefix());
 }
 }
 
 
+// Check that it can parse a line
+
+TEST_F(MessageReaderTest, ValidMessageAddDefault) {
+
+    // Add a couple of valid messages
+    reader_.processLine("GLOBAL1\t\tthis is message global one\n");
+    reader_.processLine("GLOBAL2 this is message global two");
+
+    // ... and check them
+    EXPECT_EQ(string("this is message global one"),
+        dictionary_->getText("GLOBAL1"));
+    EXPECT_EQ(string("this is message global two"),
+        dictionary_->getText("GLOBAL2"));
+    EXPECT_EQ(2, dictionary_->size());
+
+    // ... and ensure no overflows
+    vector<MessageID> overflow = dictionary_->getOverflow();
+    EXPECT_EQ(0, overflow.size());
+}
+
+TEST_F(MessageReaderTest, ValidMessageAdd) {
+
+    // Add a couple of valid messages
+    reader_.processLine("GLOBAL1\t\tthis is message global one\n",
+        MessageReader::ADD);
+    reader_.processLine("GLOBAL2 this is message global two",
+        MessageReader::ADD);
+
+    // ... and check them
+    EXPECT_EQ(string("this is message global one"),
+        dictionary_->getText("GLOBAL1"));
+    EXPECT_EQ(string("this is message global two"),
+        dictionary_->getText("GLOBAL2"));
+    EXPECT_EQ(2, dictionary_->size());
+
+    // ... and ensure no overflows
+    vector<MessageID> overflow = dictionary_->getOverflow();
+    EXPECT_EQ(0, overflow.size());
+}
+
+TEST_F(MessageReaderTest, ValidMessageReplace) {
+
+    dictionary_->add("GLOBAL1", "original global1 message");
+    dictionary_->add("GLOBAL2", "original global2 message");
+
+    // Replace a couple of valid messages
+    reader_.processLine("GLOBAL1\t\tthis is message global one\n",
+        MessageReader::REPLACE);
+    reader_.processLine("GLOBAL2 this is message global two",
+        MessageReader::REPLACE);
+
+    // ... and check them
+    EXPECT_EQ(string("this is message global one"),
+        dictionary_->getText("GLOBAL1"));
+    EXPECT_EQ(string("this is message global two"),
+        dictionary_->getText("GLOBAL2"));
+    EXPECT_EQ(2, dictionary_->size());
+
+    // ... and ensure no overflows
+    vector<MessageID> overflow = dictionary_->getOverflow();
+    EXPECT_EQ(0, overflow.size());
+}
+
+// Do checks on overflows, although this essentially duplicates the checks
+// in MessageDictionary.
+
+TEST_F(MessageReaderTest, Overflows) {
+
+    // Add a couple of valid messages
+    reader_.processLine("GLOBAL1\t\tthis is message global one\n");
+    reader_.processLine("GLOBAL2 this is message global two");
+
+    // Add a duplicate in ADD mode.
+    reader_.processLine("GLOBAL1\t\tthis is a replacement for global one");
+
+    // Replace a non-existent one in REPLACE mode
+    reader_.processLine("LOCAL\t\tthis is a new message",
+        MessageReader::REPLACE);
+
+    // Check what is in the dictionary.
+    EXPECT_EQ(string("this is message global one"),
+        dictionary_->getText("GLOBAL1"));
+    EXPECT_EQ(string("this is message global two"),
+        dictionary_->getText("GLOBAL2"));
+    EXPECT_EQ(2, dictionary_->size());
+
+    // Check what is in the overflow
+    vector<MessageID> overflow = dictionary_->getOverflow();
+    ASSERT_EQ(2, overflow.size());
+
+    sort(overflow.begin(), overflow.end());
+    EXPECT_EQ(string("GLOBAL1"), overflow[0]);
+    EXPECT_EQ(string("LOCAL"), overflow[1]);
+}

+ 61 - 19
src/lib/log/tests/stringutil_unittest.cc

@@ -18,10 +18,9 @@
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
-#include <log/stringutil.h>
+#include <log/strutil.h>
 
 
 using namespace isc;
 using namespace isc;
-using namespace isc::log;
 using namespace std;
 using namespace std;
 
 
 class StringUtilTest : public ::testing::Test {
 class StringUtilTest : public ::testing::Test {
@@ -37,15 +36,15 @@ protected:
 TEST_F(StringUtilTest, Slash) {
 TEST_F(StringUtilTest, Slash) {
 
 
     string instring = "";
     string instring = "";
-    StringUtil::normalizeSlash(instring);
+    isc::strutil::normalizeSlash(instring);
     EXPECT_EQ("", instring);
     EXPECT_EQ("", instring);
 
 
     instring = "C:\\A\\B\\C.D";
     instring = "C:\\A\\B\\C.D";
-    StringUtil::normalizeSlash(instring);
+    isc::strutil::normalizeSlash(instring);
     EXPECT_EQ("C:/A/B/C.D", instring);
     EXPECT_EQ("C:/A/B/C.D", instring);
 
 
     instring = "// \\ //";
     instring = "// \\ //";
-    StringUtil::normalizeSlash(instring);
+    isc::strutil::normalizeSlash(instring);
     EXPECT_EQ("// / //", instring);
     EXPECT_EQ("// / //", instring);
 }
 }
 
 
@@ -54,19 +53,19 @@ TEST_F(StringUtilTest, Slash) {
 TEST_F(StringUtilTest, Trim) {
 TEST_F(StringUtilTest, Trim) {
 
 
     // Empty and full string.
     // Empty and full string.
-    EXPECT_EQ("", StringUtil::trim(""));
-    EXPECT_EQ("abcxyz", StringUtil::trim("abcxyz"));
+    EXPECT_EQ("", isc::strutil::trim(""));
+    EXPECT_EQ("abcxyz", isc::strutil::trim("abcxyz"));
 
 
     // Trim right-most blanks
     // Trim right-most blanks
-    EXPECT_EQ("ABC", StringUtil::trim("ABC   "));
-    EXPECT_EQ("ABC", StringUtil::trim("ABC\t\t  \n\t"));
+    EXPECT_EQ("ABC", isc::strutil::trim("ABC   "));
+    EXPECT_EQ("ABC", isc::strutil::trim("ABC\t\t  \n\t"));
 
 
     // Left-most blank trimming
     // Left-most blank trimming
-    EXPECT_EQ("XYZ", StringUtil::trim("  XYZ"));
-    EXPECT_EQ("XYZ", StringUtil::trim("\t\t  \tXYZ"));
+    EXPECT_EQ("XYZ", isc::strutil::trim("  XYZ"));
+    EXPECT_EQ("XYZ", isc::strutil::trim("\t\t  \tXYZ"));
 
 
     // Right and left, with embedded spaces
     // Right and left, with embedded spaces
-    EXPECT_EQ("MN \t OP", StringUtil::trim("\t\tMN \t OP \t"));
+    EXPECT_EQ("MN \t OP", isc::strutil::trim("\t\tMN \t OP \t"));
 }
 }
 
 
 // Check tokenization.  Note that ASSERT_EQ is used to check the size of the
 // Check tokenization.  Note that ASSERT_EQ is used to check the size of the
@@ -77,25 +76,25 @@ TEST_F(StringUtilTest, Tokens) {
     vector<string>  result;
     vector<string>  result;
 
 
     // Default delimiters
     // Default delimiters
-    result = StringUtil::tokens(" \n ");    // Empty string
+    result = isc::strutil::tokens(" \n ");    // Empty string
     EXPECT_EQ(0, result.size());
     EXPECT_EQ(0, result.size());
 
 
-    result = StringUtil::tokens("abc");     // Full string
+    result = isc::strutil::tokens("abc");     // Full string
     ASSERT_EQ(1, result.size());
     ASSERT_EQ(1, result.size());
     EXPECT_EQ(string("abc"), result[0]);
     EXPECT_EQ(string("abc"), result[0]);
 
 
-    result = StringUtil::tokens("\t xyz \n");
+    result = isc::strutil::tokens("\t xyz \n");
     ASSERT_EQ(1, result.size());
     ASSERT_EQ(1, result.size());
     EXPECT_EQ(string("xyz"), result[0]);
     EXPECT_EQ(string("xyz"), result[0]);
 
 
-    result = StringUtil::tokens("abc\ndef\t\tghi ");
+    result = isc::strutil::tokens("abc\ndef\t\tghi ");
     ASSERT_EQ(3, result.size());
     ASSERT_EQ(3, result.size());
     EXPECT_EQ(string("abc"), result[0]);
     EXPECT_EQ(string("abc"), result[0]);
     EXPECT_EQ(string("def"), result[1]);
     EXPECT_EQ(string("def"), result[1]);
     EXPECT_EQ(string("ghi"), result[2]);
     EXPECT_EQ(string("ghi"), result[2]);
 
 
     // Non-default delimiters
     // Non-default delimiters
-    result = StringUtil::tokens("alpha/beta/ /gamma//delta/epsilon/", "/");
+    result = isc::strutil::tokens("alpha/beta/ /gamma//delta/epsilon/", "/");
     ASSERT_EQ(6, result.size());
     ASSERT_EQ(6, result.size());
     EXPECT_EQ(string("alpha"), result[0]);
     EXPECT_EQ(string("alpha"), result[0]);
     EXPECT_EQ(string("beta"), result[1]);
     EXPECT_EQ(string("beta"), result[1]);
@@ -113,10 +112,53 @@ TEST_F(StringUtilTest, ChangeCase) {
     string lower("abcdefghijklmno123[]{=+--+]}");
     string lower("abcdefghijklmno123[]{=+--+]}");
 
 
     string test = mixed;
     string test = mixed;
-    StringUtil::lowercase(test);
+    isc::strutil::lowercase(test);
     EXPECT_EQ(lower, test);
     EXPECT_EQ(lower, test);
 
 
     test = mixed;
     test = mixed;
-    StringUtil::uppercase(test);
+    isc::strutil::uppercase(test);
     EXPECT_EQ(upper, test);
     EXPECT_EQ(upper, test);
 }
 }
+
+// Formatting
+
+TEST_F(StringUtilTest, Formatting) {
+
+    vector<string> args;
+    args.push_back("arg1");
+    args.push_back("arg2");
+    args.push_back("arg3");
+
+    string format1 = "This is a string with no tokens";
+    EXPECT_EQ(format1, isc::strutil::format(format1, args));
+
+    string format2 = "";    // Empty string
+    EXPECT_EQ(format2, isc::strutil::format(format2, args));
+
+    string format3 = "   ";    // Empty string
+    EXPECT_EQ(format3, isc::strutil::format(format3, args));
+
+    string format4 = "String with %d non-string tokens %lf";
+    EXPECT_EQ(format4, isc::strutil::format(format4, args));
+
+    string format5 = "String with %s correct %s number of tokens %s";
+    string result5 = "String with arg1 correct arg2 number of tokens arg3";
+    EXPECT_EQ(result5, isc::strutil::format(format5, args));
+
+    string format6 = "String with %s too %s few tokens";
+    string result6 = "String with arg1 too arg2 few tokens";
+    EXPECT_EQ(result6, isc::strutil::format(format6, args));
+
+    string format7 = "String with %s too %s many %s tokens %s !";
+    string result7 = "String with arg1 too arg2 many arg3 tokens %s !";
+    EXPECT_EQ(result7, isc::strutil::format(format7, args));
+
+    string format8 = "String with embedded%s%s%stokens";
+    string result8 = "String with embeddedarg1arg2arg3tokens";
+    EXPECT_EQ(result8, isc::strutil::format(format8, args));
+
+    // Handle an empty vector
+    args.clear();
+    string format9 = "%s %s";
+    EXPECT_EQ(format9, isc::strutil::format(format9, args));
+}

+ 1 - 1
src/lib/log/xdebuglevel.cc

@@ -138,7 +138,7 @@ LevelPtr XDebugLevel::toLevelLS(const LogString& sArg,
         }
         }
         else {
         else {
 
 
-            // Unknown string - return default
+            // Unknown string - return default.
             return defaultLevel;
             return defaultLevel;
         }
         }
     }
     }

+ 1 - 1
src/lib/log/xdebuglevel.h

@@ -151,7 +151,7 @@ public:
     /// name is invalid, the given default is returned.
     /// name is invalid, the given default is returned.
     ///
     ///
     /// \param sArg name of the level.
     /// \param sArg name of the level.
-    /// \param defaultLevel Logging level to return if name does not exist.
+    /// \param defaultLevel Logging level to return if name doesn't exist.
     ///
     ///
     /// \return Pointer to the desired logging level object.
     /// \return Pointer to the desired logging level object.
     static LevelPtr toLevelLS(const LogString& sArg,
     static LevelPtr toLevelLS(const LogString& sArg,