1 /**********************************************************************
2 loader.cpp Plugin type "loaders"
3 and derived class OBDefine to make plugin instances from a text file
4 Copyright (C) 2008 Chris Morley
5
6 This file is part of the Open Babel project.
7 For more information, see <http://openbabel.org/>
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation version 2 of the License.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17 ***********************************************************************/
18 #include <iosfwd>
19 #include <string>
20 #include <vector>
21 #include <cstdlib>
22 #include <openbabel/oberror.h>
23 #include <openbabel/tokenst.h>
24 #include <openbabel/plugin.h>
25 #include <openbabel/locale.h>
26
27 using namespace std;
28 namespace OpenBabel
29 {
30 //A top-level plugin (with only basic capabilities)
31 class OBLoader : public OBPlugin
32 {
33 MAKE_PLUGIN(OBLoader)
34 public:
TypeID()35 const char* TypeID(){return "loaders";};
36 };
37
38 #if defined(__CYGWIN__) || defined(__MINGW32__)
39 // macro to implement static OBPlugin::PluginMapType& Map()
40 PLUGIN_CPP_FILE(OBLoader)
41 #endif
42
43 //*********************************************************
44 ///Class which makes instances of plugin classes from information in text file.
45 ///This allows the commandline and GUI interfaces to be extended without recompiling.
46 ///The class is itself a plugin but needs a short piece of code as a hook in
47 ///OBPlugin::LoadAllPlugins(). This does nothing if the plugin is not loaded.
48 class OBDefine : public OBLoader
49 {
50 public:
51 //Constructor for placeholder
OBDefine()52 OBDefine() : OBLoader("define", false), _descr("*"){}
53
54 //Main constructor
OBDefine(const char * ID,const char * filename)55 OBDefine(const char* ID, const char* filename)
56 : OBLoader(ID, false), _filename(filename)
57 {
58 ifstream ifs;
59 bool filefound = !OpenDatafile(ifs, filename).empty();
60 if(!ifs)
61 {
62 if(filefound)
63 obErrorLog.ThrowError(__FUNCTION__,string(filename) + " found but could not be opened", obError);
64 return;
65 }
66
67 // Set the locale for number parsing to avoid locale issues: PR#1785463
68 obLocale.SetLocale();
69
70 string ln;
71 while(ifs) //read entries for multiple objects
72 {
73 //More general version
74 vector<string> textlines(1);
75 while(ifs && !ReadLine(ifs, textlines[0], true)); //get first non-blank line
76 if(!ifs)
77 break;
78 while(ReadLine(ifs, ln, true )) //get subsequent lines with comments removed
79 {
80 //Concatenate with next line if ends with "\n". Otherwise each in separate string
81 if(ln.size()>=2 && ln.substr(ln.size()-2)=="\\n")
82 ln.replace(ln.size()-2, 2,"\n");//replace "\n" by '\n'
83 if(textlines.back()[textlines.back().size()-1]=='\n')
84 textlines.back() += ln; //concatenate
85 else
86 textlines.push_back(ln);
87 }
88 //look up class name in map maintained in OBPlugin
89 if(!textlines.empty() && FindDef(textlines[0].c_str()) != nullptr)
90 {
91 //Save a copy of textlines so that const char* pointers in new instance point
92 //to something which has not been deleted
93 _text.push_back(textlines);
94 // Previously, instances of the plugins were made here:
95 //_instances.push_back(pdef->MakeInstance(_text.back()));
96 // However, subsequent _text.push_back calls can cause vector reallocations,
97 // which invalidates the const char* pointers in the instances. So instead we
98 // populate _text fully before making the plugin instances.
99 }
100 else
101 obErrorLog.ThrowError(__FUNCTION__, "Failed to make an instance " + textlines[0], obError);
102 textlines.clear();
103 }
104
105 // Iterate through _text and make instances of the plugins.
106 // They will be deleted in the destructor.
107 vector<vector<string> >::iterator iter;
108 for(iter=_text.begin();iter!=_text.end();++iter) {
109 OBPlugin* pdef = FindDef((*iter)[0].c_str());
110 _instances.push_back(pdef->MakeInstance(*iter));
111 }
112
113 // return the locale to the original one
114 obLocale.RestoreLocale();
115 }
116
117
~OBDefine()118 virtual ~OBDefine()
119 {
120 std::vector<OBPlugin*>::iterator iter;
121 for(iter=_instances.begin();iter!=_instances.end();++iter)
122 delete *iter;
123 }
124
Description()125 virtual const char* Description(){ return "Makes plugin classes from a datafile"; }
126
MakeInstance(const std::vector<std::string> & textlines)127 virtual OBDefine* MakeInstance(const std::vector<std::string>& textlines)
128 {
129 OBDefine* pdef = new OBDefine(textlines[1].c_str(),textlines[2].c_str());
130
131 //The following line links the new instance to its parent, and eventually
132 //the dummy global instance. When the global instance is deleted at the end,
133 //all the other instances are too, avoiding (pseudo) memory leaks. Versions
134 //for other OBPlugin classes don't do this.
135 _instances.push_back(pdef);
136 return pdef;
137 }
138
139 private:
140 ///Returns the address of an instance of a plugin class that
141 /// has been registered as user definable
142 static OBPlugin* FindDef(const char* ID);
143
144 //return true if non-empty line read, false if empty line read or eof or error
145 static bool ReadLine(std::istream& ifs, std::string& ln, bool removeComments);
146
147 private:
148 const char* _filename;
149 const char* _descr;
150 std::vector<OBPlugin*> _instances;
151 std::vector<std::vector<std::string> > _text;
152 };
153
154 //***********************************************************
155 /* This dummy global instance has no content but is necessary to inform the
156 system of the existence of OBDefine. It cannot do the work of the plugin and
157 parse the datafile, because the plugins referred to there may not have been
158 loaded yet. Another instance with the same ID is made using MakeInstance() in
159 OBPlugin::LoadAllPlugins() after all the plugins are present.*/
160
161 OBDefine placeholderOBDefine;
162 //************************************************************
163
164 //Read line and remove comments
165 //Return true if non-empty line read, false if empty line read or eof or error
ReadLine(istream & ifs,string & ln,bool removeComments)166 bool OBDefine::ReadLine(istream& ifs, string& ln, bool removeComments)
167 {
168 if(getline(ifs, ln))
169 {
170 if(removeComments)
171 {
172 //Remove rest of line after # in first column or # followed by whitespace
173 string::size_type pos = ln.find('#');
174 if(pos!=string::npos && (pos==0 || isspace(ln[pos+1])))
175 ln.erase(pos);
176 }
177 Trim(ln);
178 return !ln.empty();
179 }
180 return false;
181 }
182
FindDef(const char * ID)183 OBPlugin* OBDefine::FindDef(const char* ID)
184 {
185 PluginIterator typeiter, iter;
186 for(typeiter=PluginMap().begin(); typeiter!=PluginMap().end();++typeiter)
187 {
188 PluginMapType map = typeiter->second->GetMap();
189 for(iter=map.begin();iter!=map.end();++iter)
190 {
191 const char* pdescr = iter->second->Description();
192 if(!pdescr)
193 continue;
194 string descr(pdescr);
195 //Matches if the ID is before the last occurrence of "definable"
196 //in the description on the same line.
197 string::size_type pos, pos2;
198 pos= descr.rfind("definable");
199 if(pos==string::npos)
200 continue;
201 pos2 = descr.rfind('\n', pos);
202 if(pos2!=string::npos && descr.substr(pos2, pos-pos2).find(ID)!=string::npos)
203 return iter->second;
204 }
205 }
206 return nullptr;
207 }
208
209 }//namespace
210
211 /*
212 Derived class OBDDefine
213 global variable with minimal constructor - no other action
214
215 In OBConversion get pointer to this class OBPlugin::GetPlugin(), which
216 has ASCII parameters, and call the vitual OBPlugin function MakeInstance
217 with plugindefines.txt as parameter to make a working instance of OBDefine.
218 This removes the dependence of OBConversion on the new OBDefine code.
219
220 Definable plugin classes have a line in their descripions
221 <classname> is definable
222 So in main constructor of OBDefine the datafile is parsed and the
223 class names looked for by scanning the descriptions of all plugin
224 classes for "is definable" and checking the name. This removes any
225 mention of OBDefine in any of the other plugin classes.
226 */
227