face: ProtocolFactory registry

refs #3904

Change-Id: Ic7c8b3d6138b7c27d4189a2e15cc646055ad1294
diff --git a/daemon/face/ethernet-factory.cpp b/daemon/face/ethernet-factory.cpp
index 53a0815..8d3e737 100644
--- a/daemon/face/ethernet-factory.cpp
+++ b/daemon/face/ethernet-factory.cpp
@@ -34,6 +34,15 @@
 namespace face {
 
 NFD_LOG_INIT("EthernetFactory");
+NFD_REGISTER_PROTOCOL_FACTORY(EthernetFactory);
+
+const std::string&
+EthernetFactory::getId()
+{
+  static std::string id("ether");
+  return id;
+}
+
 
 void
 EthernetFactory::processConfig(OptionalConfigSection configSection,
diff --git a/daemon/face/ethernet-factory.hpp b/daemon/face/ethernet-factory.hpp
index 1dec480..d1b9ee2 100644
--- a/daemon/face/ethernet-factory.hpp
+++ b/daemon/face/ethernet-factory.hpp
@@ -38,6 +38,9 @@
 class EthernetFactory : public ProtocolFactory
 {
 public:
+  static const std::string&
+  getId();
+
   /** \brief process face_system.ether config section
    */
   void
diff --git a/daemon/face/face-system.cpp b/daemon/face/face-system.cpp
index 5359e2d..a955d97 100644
--- a/daemon/face/face-system.cpp
+++ b/daemon/face/face-system.cpp
@@ -24,54 +24,31 @@
  */
 
 #include "face-system.hpp"
-// #include "core/logger.hpp"
+#include "protocol-factory.hpp"
+#include "core/logger.hpp"
 #include "fw/face-table.hpp"
 
-// ProtocolFactory includes, sorted alphabetically
-#ifdef HAVE_LIBPCAP
-#include "ethernet-factory.hpp"
-#endif // HAVE_LIBPCAP
-#include "tcp-factory.hpp"
-#include "udp-factory.hpp"
-#ifdef HAVE_UNIX_SOCKETS
-#include "unix-stream-factory.hpp"
-#endif // HAVE_UNIX_SOCKETS
-#ifdef HAVE_WEBSOCKET
-#include "websocket-factory.hpp"
-#endif // HAVE_WEBSOCKET
-
 namespace nfd {
 namespace face {
 
-// NFD_LOG_INIT("FaceSystem");
+NFD_LOG_INIT("FaceSystem");
 
 FaceSystem::FaceSystem(FaceTable& faceTable)
   : m_faceTable(faceTable)
 {
-  ///\todo #3904 make a registry, and construct instances from registry
-
-#ifdef HAVE_LIBPCAP
-  m_factories["ether"] = make_shared<EthernetFactory>();
-#endif // HAVE_LIBPCAP
-
-  m_factories["tcp"] = make_shared<TcpFactory>();
-
-  m_factories["udp"] = make_shared<UdpFactory>();
-
-#ifdef HAVE_UNIX_SOCKETS
-  m_factories["unix"] = make_shared<UnixStreamFactory>();
-#endif // HAVE_UNIX_SOCKETS
-
-#ifdef HAVE_WEBSOCKET
-  m_factories["websocket"] = make_shared<WebSocketFactory>();
-#endif // HAVE_WEBSOCKET
+  for (const std::string& id : ProtocolFactory::listRegistered()) {
+    NFD_LOG_TRACE("creating factory " << id);
+    m_factories[id] = ProtocolFactory::create(id);
+  }
 }
 
+FaceSystem::~FaceSystem() = default;
+
 std::set<const ProtocolFactory*>
 FaceSystem::listProtocolFactories() const
 {
   std::set<const ProtocolFactory*> factories;
-  for (const auto& p : m_factoryByScheme) {
+  for (const auto& p : m_factories) {
     factories.insert(p.second.get());
   }
   return factories;
@@ -88,7 +65,7 @@
 FaceSystem::getFactoryByScheme(const std::string& scheme)
 {
   auto found = m_factoryByScheme.find(scheme);
-  return found == m_factoryByScheme.end() ? nullptr : found->second.get();
+  return found == m_factoryByScheme.end() ? nullptr : found->second;
 }
 
 void
@@ -108,7 +85,7 @@
   // process sections in protocol factories
   for (const auto& pair : m_factories) {
     const std::string& sectionName = pair.first;
-    shared_ptr<ProtocolFactory> factory = pair.second;
+    ProtocolFactory* factory = pair.second.get();
 
     std::set<std::string> oldProvidedSchemes = factory->getProvidedSchemes();
     factory->processConfig(configSection.get_child_optional(sectionName), context);
@@ -116,10 +93,15 @@
     if (!isDryRun) {
       for (const std::string& scheme : factory->getProvidedSchemes()) {
         m_factoryByScheme[scheme] = factory;
-        oldProvidedSchemes.erase(scheme);
+        if (oldProvidedSchemes.erase(scheme) == 0) {
+          NFD_LOG_TRACE("factory " << sectionName <<
+                        " provides " << scheme << " FaceUri scheme");
+        }
       }
       for (const std::string& scheme : oldProvidedSchemes) {
         m_factoryByScheme.erase(scheme);
+        NFD_LOG_TRACE("factory " << sectionName <<
+                      " no longer provides " << scheme << " FaceUri scheme");
       }
     }
   }
diff --git a/daemon/face/face-system.hpp b/daemon/face/face-system.hpp
index f6a7c71..bca7702 100644
--- a/daemon/face/face-system.hpp
+++ b/daemon/face/face-system.hpp
@@ -50,6 +50,8 @@
   explicit
   FaceSystem(FaceTable& faceTable);
 
+  ~FaceSystem();
+
   /** \return ProtocolFactory objects owned by the FaceSystem
    */
   std::set<const ProtocolFactory*>
@@ -100,17 +102,15 @@
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   /** \brief config section name => protocol factory
-   *
-   *  \todo #3904 store unique_ptr<ProtocolFactory> here, and reference_wrapper<ProtocolFactory>
-   *              in m_factoryByScheme
    */
-  std::map<std::string, shared_ptr<ProtocolFactory>> m_factories;
+  std::map<std::string, unique_ptr<ProtocolFactory>> m_factories;
 
+private:
   /** \brief scheme => protocol factory
    *
    *  The same protocol factory may be available under multiple schemes.
    */
-  std::map<std::string, shared_ptr<ProtocolFactory>> m_factoryByScheme;
+  std::map<std::string, ProtocolFactory*> m_factoryByScheme;
 
   FaceTable& m_faceTable;
 };
diff --git a/daemon/face/protocol-factory.cpp b/daemon/face/protocol-factory.cpp
new file mode 100644
index 0000000..daa0214
--- /dev/null
+++ b/daemon/face/protocol-factory.cpp
@@ -0,0 +1,58 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "protocol-factory.hpp"
+#include <boost/range/adaptor/map.hpp>
+#include <boost/range/algorithm/copy.hpp>
+
+namespace nfd {
+namespace face {
+
+ProtocolFactory::Registry&
+ProtocolFactory::getRegistry()
+{
+  static Registry registry;
+  return registry;
+}
+
+unique_ptr<ProtocolFactory>
+ProtocolFactory::create(const std::string& id)
+{
+  Registry& registry = getRegistry();
+  auto found = registry.find(id);
+  return found == registry.end() ? nullptr : found->second();
+}
+
+std::set<std::string>
+ProtocolFactory::listRegistered()
+{
+  std::set<std::string> factoryIds;
+  boost::copy(getRegistry() | boost::adaptors::map_keys,
+              std::inserter(factoryIds, factoryIds.end()));
+  return factoryIds;
+}
+
+} // namespace face
+} // namespace nfd
diff --git a/daemon/face/protocol-factory.hpp b/daemon/face/protocol-factory.hpp
index 328d46e..48f755a 100644
--- a/daemon/face/protocol-factory.hpp
+++ b/daemon/face/protocol-factory.hpp
@@ -44,6 +44,31 @@
  */
 class ProtocolFactory : noncopyable
 {
+public: // registry
+  /** \brief register a protocol factory type
+   *  \tparam S subclass of ProtocolFactory
+   *  \param id factory identifier
+   */
+  template<typename PF>
+  static void
+  registerType(const std::string& id = PF::getId())
+  {
+    Registry& registry = getRegistry();
+    BOOST_ASSERT(registry.count(id) == 0);
+    registry[id] = &make_unique<PF>;
+  }
+
+  /** \return a protocol factory instance
+   *  \retval nullptr if factory with \p id is not registered
+   */
+  static unique_ptr<ProtocolFactory>
+  create(const std::string& id);
+
+  /** \return registered protocol factory ids
+   */
+  static std::set<std::string>
+  listRegistered();
+
 public:
   /**
    * \brief Base class for all exceptions thrown by protocol factories
@@ -58,6 +83,18 @@
     }
   };
 
+  virtual
+  ~ProtocolFactory() = default;
+
+#ifdef DOXYGEN
+  /** \return protocol factory id
+   *
+   *  face_system.factory-id config section is processed by the protocol factory.
+   */
+  static const std::string&
+  getId();
+#endif
+
   /** \brief process face_system subsection that corresponds to this ProtocolFactory type
    *  \param configSection the configuration section or boost::null to indicate it is omitted
    *  \param context provides access to data structures and contextual information
@@ -114,6 +151,13 @@
     return channels;
   }
 
+private: // registry
+  typedef std::function<unique_ptr<ProtocolFactory>()> CreateFunc;
+  typedef std::map<std::string, CreateFunc> Registry; // indexed by factory id
+
+  static Registry&
+  getRegistry();
+
 protected:
   /** \brief FaceUri schemes provided by this ProtocolFactory
    */
@@ -126,4 +170,18 @@
 
 } // namespace nfd
 
+/** \brief registers a protocol factory
+ *
+ *  This macro should appear once in .cpp of each protocol factory.
+ */
+#define NFD_REGISTER_PROTOCOL_FACTORY(PF)                      \
+static class NfdAuto ## PF ## ProtocolFactoryRegistrationClass \
+{                                                              \
+public:                                                        \
+  NfdAuto ## PF ## ProtocolFactoryRegistrationClass()          \
+  {                                                            \
+    ::nfd::face::ProtocolFactory::registerType<PF>();          \
+  }                                                            \
+} g_nfdAuto ## PF ## ProtocolFactoryRegistrationVariable
+
 #endif // NFD_DAEMON_FACE_PROTOCOL_FACTORY_HPP
diff --git a/daemon/face/tcp-factory.cpp b/daemon/face/tcp-factory.cpp
index a3028b5..a273e42 100644
--- a/daemon/face/tcp-factory.cpp
+++ b/daemon/face/tcp-factory.cpp
@@ -32,6 +32,14 @@
 namespace ip = boost::asio::ip;
 
 NFD_LOG_INIT("TcpFactory");
+NFD_REGISTER_PROTOCOL_FACTORY(TcpFactory);
+
+const std::string&
+TcpFactory::getId()
+{
+  static std::string id("tcp");
+  return id;
+}
 
 void
 TcpFactory::processConfig(OptionalConfigSection configSection,
diff --git a/daemon/face/tcp-factory.hpp b/daemon/face/tcp-factory.hpp
index 161c490..059c8e1 100644
--- a/daemon/face/tcp-factory.hpp
+++ b/daemon/face/tcp-factory.hpp
@@ -37,6 +37,9 @@
 class TcpFactory : public ProtocolFactory
 {
 public:
+  static const std::string&
+  getId();
+
   /** \brief process face_system.tcp config section
    */
   void
diff --git a/daemon/face/udp-factory.cpp b/daemon/face/udp-factory.cpp
index c9b4ac4..d4ee086 100644
--- a/daemon/face/udp-factory.cpp
+++ b/daemon/face/udp-factory.cpp
@@ -42,6 +42,15 @@
 namespace ip = boost::asio::ip;
 
 NFD_LOG_INIT("UdpFactory");
+NFD_REGISTER_PROTOCOL_FACTORY(UdpFactory);
+
+const std::string&
+UdpFactory::getId()
+{
+  static std::string id("udp");
+  return id;
+}
+
 
 void
 UdpFactory::processConfig(OptionalConfigSection configSection,
diff --git a/daemon/face/udp-factory.hpp b/daemon/face/udp-factory.hpp
index 7ec42b3..1e1a6ab 100644
--- a/daemon/face/udp-factory.hpp
+++ b/daemon/face/udp-factory.hpp
@@ -53,6 +53,9 @@
     }
   };
 
+  static const std::string&
+  getId();
+
   /** \brief process face_system.udp config section
    */
   void
diff --git a/daemon/face/unix-stream-factory.cpp b/daemon/face/unix-stream-factory.cpp
index b800b7c..19a39dc 100644
--- a/daemon/face/unix-stream-factory.cpp
+++ b/daemon/face/unix-stream-factory.cpp
@@ -32,6 +32,15 @@
 namespace face {
 
 NFD_LOG_INIT("UnixStreamFactory");
+NFD_REGISTER_PROTOCOL_FACTORY(UnixStreamFactory);
+
+const std::string&
+UnixStreamFactory::getId()
+{
+  static std::string id("unix");
+  return id;
+}
+
 
 void
 UnixStreamFactory::processConfig(OptionalConfigSection configSection,
diff --git a/daemon/face/unix-stream-factory.hpp b/daemon/face/unix-stream-factory.hpp
index 82d7e6e..43a6733 100644
--- a/daemon/face/unix-stream-factory.hpp
+++ b/daemon/face/unix-stream-factory.hpp
@@ -37,6 +37,9 @@
 class UnixStreamFactory : public ProtocolFactory
 {
 public:
+  static const std::string&
+  getId();
+
   /** \brief process face_system.unix config section
    */
   void
diff --git a/daemon/face/websocket-factory.cpp b/daemon/face/websocket-factory.cpp
index 1898993..86ca95e 100644
--- a/daemon/face/websocket-factory.cpp
+++ b/daemon/face/websocket-factory.cpp
@@ -32,6 +32,15 @@
 namespace ip = boost::asio::ip;
 
 NFD_LOG_INIT("WebSocketFactory");
+NFD_REGISTER_PROTOCOL_FACTORY(WebSocketFactory);
+
+const std::string&
+WebSocketFactory::getId()
+{
+  static std::string id("websocket");
+  return id;
+}
+
 
 void
 WebSocketFactory::processConfig(OptionalConfigSection configSection,
diff --git a/daemon/face/websocket-factory.hpp b/daemon/face/websocket-factory.hpp
index a31c7cd..60f0a69 100644
--- a/daemon/face/websocket-factory.hpp
+++ b/daemon/face/websocket-factory.hpp
@@ -37,6 +37,9 @@
 class WebSocketFactory : public ProtocolFactory
 {
 public:
+  static const std::string&
+  getId();
+
   /** \brief process face_system.websocket config section
    */
   void