I needed to have a case insensitive archive implementation for linux directories and also wanted a way to easily provide resource overrides. The resulting hacky code is provided here for everyone to use freely. Feel free to change and improve in any way you feel appropriate. The code was tested and works, but feel free to iron any bugs that would show up.
Table of contents
What it does
The implementation provides four new archive types which supply various transforms.
FirstOf and LastOf archives (MultiArchive class) enclose several archives into one virtual archive which uses ordered lookups for all operations (FirstOf tries to satisfy the operation searching from start - useful for prioritized loading from faster archives first think HDD install in combination with CD/DVD, LastOf works the other way round - useful for resource overrides for example).
ProxyArchive transforms the names of underlying archive. It is used in two implementations - LowerCase archive, which transforms all names to lower case, and Prefix archive, which transforms all the names by prefixing them with specified string.
Register the new archive factories
As with other user defined archive types, the factories have to be registered:
ArchiveManager::getSingleton().addArchiveFactory( new CaseLessTransformArchiveFactory() ); ArchiveManager::getSingleton().addArchiveFactory( new PrefixingTransformArchiveFactory() ); ArchiveManager::getSingleton().addArchiveFactory( new FirstOfArchiveFactory() ); ArchiveManager::getSingleton().addArchiveFactory( new LastOfArchiveFactory() );
Usage
The TransformArchive base class provides a static method parseAndCreate, which can be used to load an archive using the specification in code.
From resource specification, the outermost archive specification uses the standard Type=location convention, where location is the content of the bracket expression.
Bracket expressions
The archives use Type(parameters) convention. The parameters are archive specific:
- FirstOf, LastOf - parameters are archives that are merged into the virtual one, separated by commas
- LowerCase - parameter is an archive to transform to lower case.
- Prefix - first parameter is prefix, second is the archive to prefix.
For simplicity of implementation, no whitespace or newlines is allowed. Feel free to change.
Examples (in the bracket expression form):
LowerCase(FileSystem(c:\dir\)) Prefix(zip/,Zip(file.zip)) FirstOf(FileSystem(c:\InstallDir),FileSystem(D:\CDFILES)) LastOf(Zip(base.zip),FileSystem(Overrides\))
ProxyArchive.cpp
#include "ProxyArchive.h" #include <OgreException.h> #include <OgreString.h> #include <OgreFileSystem.h> #include <OgreArchiveManager.h> #include <OgreZip.h> #define BRACKET_OPEN '(' #define BRACKET_CLOSE ')' #define SEPARATOR ',' namespace Ogre { // ------------------------------------------------------- // ----- TransformArchive -------------------------------- // ------------------------------------------------------- TransformArchive::TransformArchive(const String& name, const String& archType) : Archive(name, archType) { } // ------------------------------------------------------- std::pair<String, String> TransformArchive::parseSpec(const String& spec) { std::pair<String, String> as; // First the contents preceding the brackets size_t pos = spec.find_first_of(BRACKET_OPEN); if (pos == String::npos) OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Archive bracket expression did not contain any brackets : '"+ spec +"'", "TransformArchive::parseSpec"); as.first = spec.substr(0, pos); // now the bracket itself. count bracket till back to zero size_t level = 1; size_t cbegin = ++pos; while (pos != spec.length()) { if (spec[pos] == BRACKET_OPEN) ++level; else if (spec[pos] == BRACKET_CLOSE) --level; if (level == 0) break; ++pos; } if (level != 0) OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Archive bracket expression - undisclosed bracket encountered", "TransformArchive::parseSpec"); as.second = spec.substr(cbegin, pos - cbegin); return as; }; // ------------------------------------------------------- bool TransformArchive::fileNameMatch(const String& pattern, const String& name) { String unt = name; StringUtil::toLowerCase(unt); String lpattern = pattern; StringUtil::toLowerCase(lpattern); // inspired by one codeproject article (but a rewrite without using the code) enum State { PM_Match = 0, PM_Any, PM_AnySeq }; // we don't wanna depend on some lib that does not exist for visual C anyway bool match = true; String::iterator pi = lpattern.begin(); String::iterator pnext = pi; // only used for AnySeq String::iterator si = unt.begin(); int state = 0; while (match && pi != lpattern.end()) { // if we're at the end of the source string if (si == unt.end()) { // match is found if pi got to end as well match = pi == lpattern.end(); break; } // see what we're comparing to state = PM_Match; if (*pi == '*') { state = PM_AnySeq; pnext = pi; // eat all the consecutive stars while (*pnext == '*') ++pnext; } else if (*pi == '?') { state = PM_Any; } switch (state) { case PM_Match: match = *pi == *si; case PM_Any: ++pi; ++si; break; case PM_AnySeq: // matched any character ++si; // found the end of pattern? If so, // then the previous comparisons decide if (pnext == lpattern.end()) { match = true; break; } // end for string, but some pattern following? if (pnext != lpattern.end() && si == unt.end()) { match = false; break; } // or found a match after the star? if (*si == *pnext) ++pi; break; } } return match; } // ------------------------------------------------------- Archive* TransformArchive::parseAndCreate(const String& spec) { // Strip out our parameters std::pair<String, String> as = parseSpec(spec); return ArchiveManager::getSingleton().load(as.second, as.first); } // ------------------------------------------------------- std::vector<String> TransformArchive::splitParams(const String& spec) { // by hand, nicer implementation would be nice // now the bracket itself. count bracket till back to zero size_t level = 1; size_t pos = 0, last_pos = 0; std::vector<String> result; while (pos != spec.length()) { if (spec[pos] == BRACKET_OPEN) ++level; else if (spec[pos] == BRACKET_CLOSE) --level; // split on level one commas only if (spec[pos] == SEPARATOR && level == 1) { String token = spec.substr(last_pos, pos++); last_pos = pos; result.push_back(token); } if (level == 0) break; ++pos; } if (last_pos != pos) { if (level < 1) pos--; String token = spec.substr(last_pos, pos); result.push_back(token); } return result; } // ------------------------------------------------------- // ----- Proxy Archive ----------------------------------- // ------------------------------------------------------- ProxyArchive::ProxyArchive(const String& name, const String& archType) : TransformArchive(name, archType), mArchive(NULL) { } // ------------------------------------------------------- ProxyArchive::~ProxyArchive(void) { if (mArchive) { ArchiveManager::getSingleton().unload(mArchive); } } // ------------------------------------------------------- void ProxyArchive::load(void) { if (!mArchive) { mArchive = parseAndCreate(mName); } // load, build map mArchive->load(); StringVectorPtr lst = mArchive->list(true, false); StringVector::iterator it = lst->begin(); while (it != lst->end()) { const std::string& fn = *it++; std::string tn = transformName(fn); StringUtil::toLowerCase(tn); // insert into the map std::pair<NameTable::iterator, bool> result = mExtToIntNames.insert(std::make_pair(tn, fn)); // see if the result is ok, except if not if (!result.second) OGRE_EXCEPT(Exception::ERR_DUPLICATE_ITEM, "Archive '" + mName + "' contains duplicities : " + fn, "ProxyArchive::load"); } } // ------------------------------------------------------- void ProxyArchive::unload(void) { // we don't unload the underlying archive as the Archive manager // is not prepared to have archive to archive dependencies and that results in segv here mExtToIntNames.clear(); } // ------------------------------------------------------- FileInfoListPtr ProxyArchive::findFileInfo(const String& pattern, bool recursive , bool dirs) { /// have to list all infos, filter those which fit FileInfoListPtr lst = listFileInfo(recursive, dirs); FileInfoListPtr res(new FileInfoList()); /// now iterate, the list, match using the name, return FileInfoList::iterator it = lst->begin(); while (it != lst->end()) { const FileInfo& fi = *it++; // match? if (match(pattern, fi.filename)) { res->push_back(fi); } } return res; } // ------------------------------------------------------- bool ProxyArchive::exists(const String& filename) { String un; if (untransformName(filename, un)) return mArchive->exists(un); else return false; } // ------------------------------------------------------- StringVectorPtr ProxyArchive::find(const String& pattern, bool recursive , bool dirs) { /// have to list all infos, filter those which fit StringVectorPtr lst = list(recursive, dirs); StringVectorPtr res(new StringVector()); /// now iterate, the list, match using the name, return StringVector::iterator it = lst->begin(); while (it != lst->end()) { const String& fn = *it++; // match? if (match(pattern, fn)) { res->push_back(fn); } } return res; } // ------------------------------------------------------- FileInfoListPtr ProxyArchive::listFileInfo(bool recursive , bool dirs) { FileInfoListPtr list = mArchive->listFileInfo(recursive, dirs); FileInfoListPtr res(new FileInfoList()); /// now iterate, the list, match using the name, return FileInfoList::iterator it = list->begin(); while (it != list->end()) { const FileInfo& fi = *it++; FileInfo fin; fin.archive = this; fin.filename = transformName(fi.filename); fin.path = transformName(fi.path); fin.basename = transformName(fi.basename); fin.compressedSize = fi.compressedSize; fin.uncompressedSize = fi.uncompressedSize; res->push_back(fin); } return res; } // ------------------------------------------------------- StringVectorPtr ProxyArchive::list(bool recursive , bool dirs) { /// have to list all infos, filter those which fit StringVectorPtr lst = mArchive->list(recursive, dirs); StringVectorPtr res(new StringVector()); /// now iterate, the list, match using the name, return StringVector::iterator it = lst->begin(); while (it != lst->end()) { const String& fn = *it++; res->push_back(transformName(fn)); } return res; } // ------------------------------------------------------- DataStreamPtr ProxyArchive::open(const String& filename) const { String un; if (untransformName(filename, un)) return mArchive->open(un); else return DataStreamPtr(); } // ------------------------------------------------------- bool ProxyArchive::untransformName(const String& name, String& unt) const { String s = name; StringUtil::toLowerCase(s); NameTable::const_iterator it = mExtToIntNames.find(s); if (it != mExtToIntNames.end()) { unt = it->second; return true; } else { return false; } } // ------------------------------------------------------- bool ProxyArchive::match(const String& pattern, const String& name) const { String unt; if (!untransformName(name, unt)) return false; return fileNameMatch(pattern, unt); } // ------------------------------------------------------- // ----- MultiArchive -------------------------------- // ------------------------------------------------------- MultiArchive::MultiArchive(const String& name, const String& archType, bool reverse) : TransformArchive(name, archType), mReverseOrder(reverse) { }; // ------------------------------------------------------- MultiArchive::~MultiArchive() { /** ArchiveList::iterator it = mArchiveList.begin(); for (; it != mArchiveList.end(); ++it) { if (*it) ArchiveManager::getSingleton().unload((*it)); } */ mArchiveList.clear(); }; // ------------------------------------------------------- void MultiArchive::load() { std::vector<String> sv = splitParams(mName); std::vector<String>::iterator it; // make archives for all the found ones for (it = sv.begin(); it != sv.end(); ++it) { // each splitted archive is parsed and processed // Strip out our parameters Archive *arch = parseAndCreate(*it); mArchiveList.push_back(arch); } } // ------------------------------------------------------- void MultiArchive::unload(void) { // we don't unload the underlying archives as the Archive manager // is not prepared to have archive to archive dependencies and that results in potential segv here mArchiveList.clear(); } // ------------------------------------------------------- DataStreamPtr MultiArchive::open(const String& filename) const { Archive* a = getArchiveForFileName(filename); if (a != NULL) return a->open(filename); OGRE_EXCEPT(Exception::ERR_FILE_NOT_FOUND, "Invalid file name specified", "MultiArchive::open"); }; // ------------------------------------------------------- StringVectorPtr MultiArchive::list(bool recursive, bool dirs) { // We collect the results using the defined order... ArchiveList::iterator it = mArchiveList.begin(); std::set<String> resultset; for (;it != mArchiveList.end(); ++it) { // list the archive Archive *a = *it; // order strategy does not matter here, just merge the results StringVectorPtr svp = a->list(recursive, dirs); for(StringVector::iterator sit = svp->begin(); sit != svp->end(); ++sit) { resultset.insert(*sit); } } // convert to vector, return std::set<String>::iterator si = resultset.begin(); std::set<String>::iterator se = resultset.end(); StringVectorPtr sv(new StringVector()); for (; si != se; ++si) { sv->push_back(*si); } return sv; }; // ------------------------------------------------------- FileInfoListPtr MultiArchive::listFileInfo(bool recursive, bool dirs) { ArchiveList::iterator it = mArchiveList.begin(); std::map<String, FileInfo> resultmap; for (;it != mArchiveList.end(); ++it) { // list the archive Archive *a = *it; // order strategy does matter here. We either replace the FileInfo or not FileInfoListPtr svp = a->listFileInfo(recursive, dirs); for(FileInfoList::iterator sit = svp->begin(); sit != svp->end(); ++sit) { const String& fn = sit->filename; if (mReverseOrder) { resultmap[fn] = *sit; } else { // see if we already have the result std::map<String, FileInfo>::iterator mi = resultmap.find(fn); if (mi == resultmap.end()) { resultmap.insert(std::make_pair(fn, *sit)); } } } } // convert to vector, return std::map<String, FileInfo>::iterator si = resultmap.begin(); std::map<String, FileInfo>::iterator se = resultmap.end(); FileInfoListPtr sv(new FileInfoList()); for (; si != se; ++si) { sv->push_back(si->second); } return sv; }; // ------------------------------------------------------- StringVectorPtr MultiArchive::find(const String& pattern, bool recursive, bool dirs) { // We collect the results using the defined order... ArchiveList::iterator it = mArchiveList.begin(); std::set<String> resultset; for (;it != mArchiveList.end(); ++it) { // list the archive Archive *a = *it; // order strategy does not matter here, just merge the results StringVectorPtr svp = a->find(pattern, recursive, dirs); for(StringVector::iterator sit = svp->begin(); sit != svp->end(); ++sit) { resultset.insert(*sit); } } // convert to vector, return std::set<String>::iterator si = resultset.begin(); std::set<String>::iterator se = resultset.end(); StringVectorPtr sv(new StringVector()); for (; si != se; ++si) { sv->push_back(*si); } return sv; }; // ------------------------------------------------------- bool MultiArchive::exists(const String& filename) { Archive* a = getArchiveForFileName(filename); return a != NULL; }; // ------------------------------------------------------- FileInfoListPtr MultiArchive::findFileInfo(const String& pattern, bool recursive, bool dirs) { ArchiveList::iterator it = mArchiveList.begin(); std::map<String, FileInfo> resultmap; for (;it != mArchiveList.end(); ++it) { // list the archive Archive *a = *it; // order strategy does matter here. We either replace the FileInfo or not FileInfoListPtr svp = a->findFileInfo(pattern, recursive, dirs); for(FileInfoList::iterator sit = svp->begin(); sit != svp->end(); ++sit) { const String& fn = sit->filename; if (mReverseOrder) { resultmap[fn] = *sit; } else { // see if we already have the result std::map<String, FileInfo>::iterator mi = resultmap.find(fn); if (mi == resultmap.end()) { resultmap.insert(std::make_pair(fn, *sit)); } } } } // convert to vector, return std::map<String, FileInfo>::iterator si = resultmap.begin(); std::map<String, FileInfo>::iterator se = resultmap.end(); FileInfoListPtr sv(new FileInfoList()); for (; si != se; ++si) { sv->push_back(si->second); } return sv; }; // ------------------------------------------------------- bool MultiArchive::isCaseSensitive(void) const { return true; }; // ------------------------------------------------------- time_t MultiArchive::getModifiedTime(const String& filename) { Archive* a = getArchiveForFileName(filename); if (a != NULL) return a->getModifiedTime(filename); OGRE_EXCEPT(Exception::ERR_FILE_NOT_FOUND, "Invalid file name specified", "MultiArchive::getModifiedTime"); }; // ------------------------------------------------------- Archive* MultiArchive::getArchiveForFileName(const std::string& name) const { // TODO: This could also be done dynamically on demand Archive* target = NULL; ArchiveList::const_iterator it = mArchiveList.begin(); ArchiveList::const_iterator eit = mArchiveList.end(); for (; it != eit; ++it) { // search for the file Archive* ca = *it; if (ca->exists(name)) { target = ca; // if going forward, the first match is the one // if going backward, the last one is if (!mReverseOrder) break; } } return target; }; // ------------------------------------------------------- // ----- CaseLess Archive -------------------------------- // ------------------------------------------------------- CaseLessTransformArchive::CaseLessTransformArchive(const String& name, const String& archType) : ProxyArchive(name, archType) { } // ------------------------------------------------------- bool CaseLessTransformArchive::isCaseSensitive(void) const { return false; } // ------------------------------------------------------- String CaseLessTransformArchive::transformName(const std::string& name) { // simple - just lowercase the name String res = name; StringUtil::toLowerCase(res); return res; } // ------------------------------------------------------- // ----- Prefixing Archive -------------------------------- // ------------------------------------------------------- PrefixingTransformArchive::PrefixingTransformArchive(const String& name, const String& archType) : ProxyArchive(name, archType) { // extract the prefix std::vector<String> split = splitParams(name); if (split.size() != 2) OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, "Archive expression did not contain required parameters : '"+ name +"'", "PrefixingTransformArchive::PrefixingTransformArchive"); mPrefix = split[0]; mName = split[1]; } // ------------------------------------------------------- bool PrefixingTransformArchive::isCaseSensitive(void) const { return true; } // ------------------------------------------------------- String PrefixingTransformArchive::transformName(const std::string& name) { return mPrefix + name; } // ------------------------------------------------------- // ----- Factory Stuff ----------------------------------- // ------------------------------------------------------- const String CaseLessTransformArchiveFactory::msTypeName = "LowerCase"; const String& CaseLessTransformArchiveFactory::getType(void) const { return msTypeName; } const String PrefixingTransformArchiveFactory::msTypeName = "Prefix"; const String& PrefixingTransformArchiveFactory::getType(void) const { return msTypeName; } const String FirstOfArchiveFactory::msTypeName = "FirstOf"; const String& FirstOfArchiveFactory::getType(void) const { return msTypeName; } const String LastOfArchiveFactory::msTypeName = "LastOf"; const String& LastOfArchiveFactory::getType(void) const { return msTypeName; } }
ProxyArchive.h
#ifndef __PROXYARCHIVE_H #define __PROXYARCHIVE_H #include <OgreArchive.h> #include <OgreArchiveFactory.h> #include <OgreString.h> #define __EXPORT namespace Ogre { /** Base class for all transforming archives. Contains the common methods used in all implementation (namely the bracket parser). * @note This class and all the descendants use archive specification language in form Type(parameters), where parameters are Type specific. * @author Volca */ class __EXPORT TransformArchive : public Archive { public: TransformArchive(const String& name, const String& archType); /** Explodes the archive specification in the form "Type(contents)". The type is then understood as Archive Type, the contents * are passed as the archive name. * @param spec The string representation of the archive spec * @return pair The pair containing type and contents strings forming the specification: "first(second)" * @throw Ogre::Exception if the format is wrong */ static std::pair<String, String> parseSpec(const String& spec); /** Does a wildcard match of the * pattern with the 'name' string * @param pattern The wildcard pattern, in which '*' means "anything" * @param name The name of the file to compare */ static bool fileNameMatch(const String& pattern, const String& name); /** Parses the specification string and creates an archive based on it. */ static Archive* parseAndCreate(const String& spec); /** Splits the specified string on commas, obeying commas burried in enclosing brackets. */ std::vector<String> splitParams(const String& spec); }; /** Proxy archive, which performs name transforms and delegates to underlying archive instance. * There are couple different possible usages for this archive class: * <ul> * <li>A case insensitive filesystem archive on *nix systems</li> * <li>A ZIP file handler which includes the FNAME.ZIP as FNAME/ in all names of the archive</li> * <li>Other</li> * </ul> * @author Volca */ class __EXPORT ProxyArchive : public TransformArchive { public: /// constructor ProxyArchive(const String& name, const String& archType); /// destructor virtual ~ProxyArchive(void); /// performs the archive load. Scans the archive for filenames, builds the reverse transform table virtual void load(void); /// Unloads the archive, clears the transform map virtual void unload(void); /// opens a resource stream, unmapping the name first virtual DataStreamPtr open(const String& filename) const; /// lists the contents of the archive. transformed. virtual StringVectorPtr list(bool recursive = true, bool dirs = false); /// lists the contents of the archive. transformed, in the FileInfo structures. virtual FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false); /// performs a pattern match find on the archive files virtual StringVectorPtr find(const String& pattern, bool recursive = true, bool dirs = false); /// Searches for the given name, untransforming it first virtual bool exists(const String& filename); /** Searches for files that match the given pattern * @see find */ virtual FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, bool dirs = false); /// reports case sensitiveness of this proxy archive virtual bool isCaseSensitive(void) const = 0; time_t getModifiedTime(const String& filename) {return 0;}; protected: /// performs the forward transform on a single name virtual String transformName(const String& name) = 0; /// performs an inverse transform on a single name, turning internal name from the external one virtual bool untransformName(const String& name, String& unt) const; /** compares if the given filename matches the pattern * By default, this does case insensitive filename match on the untransformed name */ virtual bool match(const String& pattern, const String& name) const; typedef std::map<std::string, std::string> NameTable; NameTable mExtToIntNames; Archive* mArchive; }; /** Content merging archive. A base class for various archives which map multiple archives into one. * For this to work, we have a step which maps each file name to an archive pointer * @author Volca */ class __EXPORT MultiArchive : public TransformArchive { public: /// Constructor MultiArchive(const String& name, const String& archType, bool reverse); /// Destrucor ~MultiArchive(); /// performs the archive load. Scans the archive for filenames, builds the reverse transform table virtual void load(void); /// Unloads the archive, clears the transform map virtual void unload(void); /// opens a resource stream from the archive mapped to the filename (case sensitive) virtual DataStreamPtr open(const String& filename) const; /// lists the contents of the archive virtual StringVectorPtr list(bool recursive = true, bool dirs = false); /// lists the contents of the archive. That means contents of all the archives merged, in the FileInfo structures. virtual FileInfoListPtr listFileInfo(bool recursive = true, bool dirs = false); /// performs a pattern match find on the archive files virtual StringVectorPtr find(const String& pattern, bool recursive = true, bool dirs = false); /// Searches for the given file name virtual bool exists(const String& filename); /** Searches for files that match the given pattern * @see find */ virtual FileInfoListPtr findFileInfo(const String& pattern, bool recursive = true, bool dirs = false); /// reports case sensitiveness of this proxy archive virtual bool isCaseSensitive(void) const; /// reports the modification time of the given file time_t getModifiedTime(const String& filename); protected: /// Gets the archive for the specified file name, or NULL if file name is invalid Archive* getArchiveForFileName(const std::string& name) const; typedef std::list<Archive*> ArchiveList; /// List of archives present ArchiveList mArchiveList; /// Take the last archive as the first one (reversed order of evaluation) bool mReverseOrder; }; /** Lowercase transforming file system archive. * @author Volca */ class __EXPORT CaseLessTransformArchive : public ProxyArchive { public: CaseLessTransformArchive(const String& name, const String& archType); bool isCaseSensitive(void) const; protected: String transformName(const std::string& name); }; /** String prefixing file system archive. Prefixes all file names in the archive with a custom specified string (the contents of the file name up to the first comma are used) * @author Volca */ class __EXPORT PrefixingTransformArchive : public ProxyArchive { public: PrefixingTransformArchive(const String& name, const String& archType); bool isCaseSensitive(void) const; protected: String transformName(const std::string& name); String mPrefix; }; // Factories, so we can actually use these class __EXPORT CaseLessTransformArchiveFactory : public ArchiveFactory { public: virtual ~CaseLessTransformArchiveFactory() { } const String& getType(void) const; Archive* createInstance(const String& name) { return new CaseLessTransformArchive(name, msTypeName); } void destroyInstance( Archive* arch) { delete arch; } protected: static const String msTypeName; }; class __EXPORT PrefixingTransformArchiveFactory : public ArchiveFactory { public: virtual ~PrefixingTransformArchiveFactory() { } const String& getType(void) const; Archive* createInstance(const String& name) { return new PrefixingTransformArchive(name, msTypeName); } void destroyInstance( Archive* arch) { delete arch; } protected: static const String msTypeName; }; class __EXPORT FirstOfArchiveFactory : public ArchiveFactory { public: virtual ~FirstOfArchiveFactory() {} const String& getType(void) const; Archive* createInstance(const String& name) { return new MultiArchive(name, msTypeName, false); } void destroyInstance( Archive* arch) { delete arch; } protected: static const String msTypeName; }; class __EXPORT LastOfArchiveFactory : public ArchiveFactory { public: virtual ~LastOfArchiveFactory() {} const String& getType(void) const; Archive* createInstance(const String& name) { return new MultiArchive(name, msTypeName, true); } void destroyInstance( Archive* arch) { delete arch; } protected: static const String msTypeName; }; } #endif
Alias:ProxyArchive