639d639f9601 — Chris Cannam 6 years ago
Initial commit of bqthingfactory
1 files changed, 204 insertions(+), 0 deletions(-)

A => bqthingfactory/ThingFactory.h
A => bqthingfactory/ThingFactory.h +204 -0
@@ 0,0 1,204 @@ 
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+/* Copyright Chris Cannam - All Rights Reserved */
+
+#ifndef BQ_THING_FACTORY_H
+#define BQ_THING_FACTORY_H
+
+#include <string>
+#include <set>
+#include <map>
+#include <vector>
+#include <iostream>
+
+namespace breakfastquay {
+
+template <typename Thing, typename Parameters>
+class AbstractThingBuilder;
+
+class UnknownThingException : virtual public std::exception {
+public:
+    UnknownThingException(std::string uri) throw() { m_w = ("Unknown thing: " + uri); }
+    virtual ~UnknownThingException() throw() { }
+    virtual const char *what() const throw() { return m_w.c_str(); }
+protected:
+    std::string m_w;
+};
+
+class UnknownTagException : virtual public std::exception {
+public:
+    UnknownTagException(std::string tag) throw() { m_w = ("Unknown tag: " + tag); }
+    virtual ~UnknownTagException() throw() { }
+    virtual const char *what() const throw() { return m_w.c_str(); }
+protected:
+    std::string m_w;
+};
+
+
+/**
+ * A factory for objects from classes that share a common base class,
+ * have identical single-argument constructors, can be identified by
+ * URI, and that can register their existence with the factory (so
+ * that the factory does not have to know about all buildable
+ * classes).
+ *
+ * ** How to use these classes **
+ *
+ * Given a base class A with many subclasses B, C, D, etc, all of
+ * which need to be passed parameters class P in their constructor:
+ *
+ *  -- in a header associated with A,
+ *
+ *     - Create a template class ABuilder<T> which inherits from
+ *       ConcreteThingBuilder<T, A, P>.  This is your class which will
+ *       be specialised to provide a builder for each subclass of A.
+ *       Its constructor must accept a std::string containing the URI
+ *       that identifies the class of object being built, which is
+ *       passed to the parent class's constructor.  Optionally, it may
+ *       also accept and pass to the parent class a vector<string> of
+ *       "tags", which are strings used to identify the sorts of
+ *       facility this builder's object supports -- for example, file
+ *       extensions or MIME types that the object can parse.  If two
+ *       builders register support for the same tag, only the first to
+ *       register will be used (note that which one this is may depend
+ *       on static object construction ordering, so it's generally
+ *       better if tags are unique to a builder).
+ * 
+ *     - You may also wish to typedef ThingFactory<A, P> to something
+ *       like AFactory, for convenience.
+ *
+ *  -- in a .cpp file associated with each of B, C, D etc,
+ *
+ *     - Define a static variable of class ABuilder<B>, ABuilder<C>,
+ *       ABuilder<D>, etc, passing the class's identifying URI and
+ *       optional supported tag list to its constructor.  (If you
+ *       like, this could be a static member of some class.)
+ *
+ * You can then do the following:
+ *
+ *  -- call AFactory::getInstance()->getURIs() to retrieve a list of
+ *     all registered URIs for this factory.
+ * 
+ *  -- call AFactory::getInstance()->create(uri, parameters), where
+ *     parameters is an object of type P, to construct a new object
+ *     whose class is that associated with the URI uri.
+ *
+ *  -- call AFactory::getInstance()->getTags() to retrieve a list of
+ *     all tags known to be supported by some builder.  Remember that
+ *     builders do not have to support any tags and many builders
+ *     could support the same tag, so you cannot retrieve all builders
+ *     by starting from the tags list: use getURIs for that.
+ *
+ *  -- call AFactory::getInstance()->getURIFor(tag), where tag is a
+ *     std::string corresponding to one of the tags supported by some
+ *     builder, to obtain the URI of the first builder to have
+ *     registered its support for the given tag.
+ *  
+ *  -- call AFactory::getInstance()->createFor(tag, parameters), where
+ *     tag is a std::string corresponding to one of the tags supported by
+ *     some builder and parameters is an object of type P, to
+ *     construct a new object whose class is that built by the first
+ *     builder to have registered its support for the given tag. 
+ */
+template <typename Thing, typename Parameters>
+class ThingFactory
+{
+protected:
+    typedef AbstractThingBuilder<Thing, Parameters> Builder;
+    typedef std::map<std::string, Builder *> BuilderMap;
+    typedef std::map<std::string, std::string> TagURIMap;
+
+public:
+    typedef std::set<std::string> URISet;
+
+    static ThingFactory<Thing, Parameters> *getInstance() {
+        static ThingFactory<Thing, Parameters> instance;
+	return &instance;
+    }
+    
+    URISet getURIs() const {
+        std::vector<std::string> keys = m_registry.keys();
+	URISet s;
+	for (size_t i = 0; i < keys.size(); ++i) s.insert(keys[i]);
+	return s;
+    }
+    
+    std::vector<std::string> getTags() const {
+        return m_tags.keys();
+    }
+    
+    std::string getURIFor(std::string tag) const {
+        if (m_tags.find(tag) == m_tags.end()) {
+            throw UnknownTagException(tag);
+        }
+        return m_tags[tag];
+    }
+
+    Thing *create(std::string uri, Parameters p) const {
+	if (m_registry.find(uri) == m_registry.end()) {
+	    throw UnknownThingException(uri);
+	}
+	return m_registry[uri]->build(p);
+    }
+
+    Thing *createFor(std::string tag, Parameters p) const {
+        return create(getURIFor(tag), p);
+    }
+
+    void registerBuilder(std::string uri, Builder *builder) {
+        if (m_registry.find(uri) != m_registry.end()) {
+            std::cerr << "Turbot::ThingFactory::registerBuilder: WARNING: Duplicate URI: "
+                      << uri << std::endl;
+        }
+	m_registry[uri] = builder;
+    }
+    
+    void registerBuilder(std::string uri, Builder *builder, std::vector<std::string> tags) {
+        std::cerr << "ThingFactory::registerBuilder: uri " << uri << " (" << tags.size() << " tag(s))" << std::endl;
+        if (m_registry.find(uri) != m_registry.end()) {
+            std::cerr << "Turbot::ThingFactory::registerBuilder: WARNING: Duplicate URI: "
+                      << uri << " (with tags: ";
+            for (size_t i = 0; i < tags.size(); ++i) {
+                std::cerr << tags[i] << " ";
+            }
+            std::cerr << ")" << std::endl;
+        }
+	m_registry[uri] = builder;
+        for (size_t i = 0; i < tags.size(); ++i) {
+            if (m_tags.find(tags[i]) != m_registry.end()) continue;
+            std::cerr << "ThingFactory::registerBuilder: tag " << tags[i] 
+                      << " -> " << uri << std::endl;
+            m_tags[tags[i]] = uri;
+        }
+    }
+    
+protected:
+    BuilderMap m_registry;
+    TagURIMap m_tags;
+};
+
+template <typename Thing, typename Parameters>
+class AbstractThingBuilder
+{
+public:
+    virtual ~AbstractThingBuilder() { }
+    virtual Thing *build(Parameters) = 0;
+};
+
+template <typename ConcreteThing, typename Thing, typename Parameters>
+class ConcreteThingBuilder : public AbstractThingBuilder<Thing, Parameters>
+{
+public:
+    ConcreteThingBuilder(std::string uri) {
+	ThingFactory<Thing, Parameters>::getInstance()->registerBuilder(uri, this);
+    }
+    ConcreteThingBuilder(std::string uri, std::vector<std::string> tags) {
+	ThingFactory<Thing, Parameters>::getInstance()->registerBuilder(uri, this, tags);
+    }
+    virtual Thing *build(Parameters p) {
+	return new ConcreteThing(p);
+    }
+};
+
+}
+
+#endif