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