utils/library.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2007-2013 by Johan De Taeye, frePPLe bvba *
4  * *
5  * This library is free software; you can redistribute it and/or modify it *
6  * under the terms of the GNU Affero General Public License as published *
7  * by the Free Software Foundation; either version 3 of the License, or *
8  * (at your option) any later version. *
9  * *
10  * This library is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU Affero General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU Affero General Public *
16  * License along with this program. *
17  * If not, see <http://www.gnu.org/licenses/>. *
18  * *
19  ***************************************************************************/
20 
21 #define FREPPLE_CORE
22 #include "frepple/utils.h"
23 #include <sys/stat.h>
24 
25 // These headers are required for the loading of dynamic libraries and the
26 // detection of the number of cores.
27 #ifdef WIN32
28 #include <windows.h>
29 #else
30 #include <dlfcn.h>
31 #include <unistd.h>
32 #endif
33 
34 
35 namespace frepple
36 {
37 namespace utils
38 {
39 
40 // Repository of all categories and commands
41 DECLARE_EXPORT const MetaCategory* MetaCategory::firstCategory = NULL;
42 DECLARE_EXPORT MetaCategory::CategoryMap MetaCategory::categoriesByTag;
43 DECLARE_EXPORT MetaCategory::CategoryMap MetaCategory::categoriesByGroupTag;
44 
45 // Repository of loaded modules
46 DECLARE_EXPORT set<string> Environment::moduleRegistry;
47 
48 // Number of processors.
49 // The value initialized here is updated when the getProcessorCores function
50 // is called the first time.
51 DECLARE_EXPORT int Environment::processorcores = -1;
52 
53 // Output logging stream, whose input buffer is shared with either
54 // Environment::logfile or cout.
55 DECLARE_EXPORT ostream logger(cout.rdbuf());
56 
57 // Output file stream
58 DECLARE_EXPORT ofstream Environment::logfile;
59 
60 // Name of the log file
61 DECLARE_EXPORT string Environment::logfilename;
62 
63 // Hash value computed only once
64 DECLARE_EXPORT const hashtype MetaCategory::defaultHash(Keyword::hash("default"));
65 
66 vector<PythonType*> PythonExtensionBase::table;
67 
68 
70 {
71  // Initialize only once
72  static bool init = false;
73  if (init)
74  {
75  logger << "Warning: Calling frepple::LibraryUtils::initialize() more "
76  << "than once." << endl;
77  return;
78  }
79  init = true;
80 
81  // Set the locale to the default setting.
82  // When not executed, the locale is the "C-locale", which restricts us to
83  // ascii data in the input.
84  // For Posix platforms the environment variable LC_ALL controls the locale.
85  // Most Linux distributions these days have a default locale that supports
86  // UTF-8 encoding, meaning that every unicode character can be
87  // represented.
88  // On Windows, the default is the system-default ANSI code page. The number
89  // of characters that frePPLe supports on Windows is constrained by this...
90 #if defined(HAVE_SETLOCALE) || defined(_MSC_VER)
91  setlocale(LC_ALL, "" );
92 #endif
93 
94  // Initialize Xerces parser
95  xercesc::XMLPlatformUtils::Initialize();
96 
97  // Initialize the Python interpreter
99 
100  // Register new methods in Python
102  "loadmodule", loadModule, METH_VARARGS,
103  "Dynamically load a module in memory.");
104 }
105 
106 
107 DECLARE_EXPORT string Environment::searchFile(const string filename)
108 {
109 #ifdef _MSC_VER
110  static char pathseperator = '\\';
111 #else
112  static char pathseperator = '/';
113 #endif
114 
115  // First: check the current directory
116  struct stat stat_p;
117  int result = stat(filename.c_str(), &stat_p);
118  if (!result && (stat_p.st_mode & S_IREAD))
119  return filename;
120 
121  // Second: check the FREPPLE_HOME directory, if it is defined
122  string fullname;
123  char * envvar = getenv("FREPPLE_HOME");
124  if (envvar)
125  {
126  fullname = envvar;
127  if (*fullname.rbegin() != pathseperator)
128  fullname += pathseperator;
129  fullname += filename;
130  result = stat(fullname.c_str(), &stat_p);
131  if (!result && (stat_p.st_mode & S_IREAD))
132  return fullname;
133  }
134 
135 #ifdef DATADIRECTORY
136  // Third: check the data directory
137  fullname = DATADIRECTORY;
138  if (*fullname.rbegin() != pathseperator)
139  fullname += pathseperator;
140  fullname.append(filename);
141  result = stat(fullname.c_str(), &stat_p);
142  if (!result && (stat_p.st_mode & S_IREAD))
143  return fullname;
144 #endif
145 
146 #ifdef LIBDIRECTORY
147  // Fourth: check the lib directory
148  fullname = LIBDIRECTORY;
149  if (*fullname.rbegin() != pathseperator)
150  fullname += pathseperator;
151  fullname += "frepple";
152  fullname += pathseperator;
153  fullname += filename;
154  result = stat(fullname.c_str(), &stat_p);
155  if (!result && (stat_p.st_mode & S_IREAD))
156  return fullname;
157 #endif
158 
159 #ifdef SYSCONFDIRECTORY
160  // Fifth: check the sysconf directory
161  fullname = SYSCONFDIRECTORY;
162  if (*fullname.rbegin() != pathseperator)
163  fullname += pathseperator;
164  fullname += filename;
165  result = stat(fullname.c_str(), &stat_p);
166  if (!result && (stat_p.st_mode & S_IREAD))
167  return fullname;
168 #endif
169 
170  // Not found
171  return "";
172 }
173 
174 
176 {
177  // Previously detected already
178  if (processorcores >= 1) return processorcores;
179 
180  // Detect the number of cores on the machine
181 #ifdef WIN32
182  // Windows
183  SYSTEM_INFO sysinfo;
184  GetSystemInfo(&sysinfo);
185  processorcores = sysinfo.dwNumberOfProcessors;
186 #else
187  // Linux, Solaris and AIX.
188  // Tough luck for other platforms.
189  processorcores = sysconf(_SC_NPROCESSORS_ONLN);
190 #endif
191  // Detection failed...
192  if (processorcores<1) processorcores = 1;
193  return processorcores;
194 }
195 
196 
198 {
199  // Bye bye message
200  if (!logfilename.empty())
201  logger << "Stop logging at " << Date::now() << endl;
202 
203  // Close an eventual existing log file.
204  if (logfile.is_open()) logfile.close();
205 
206  // No new logfile specified: redirect to the standard output stream
207  if (x.empty() || x == "+")
208  {
209  logfilename = x;
210  logger.rdbuf(cout.rdbuf());
211  return;
212  }
213 
214  // Open the file: either as a new file, either appending to existing file
215  if (x[0] != '+') logfile.open(x.c_str(), ios::out);
216  else logfile.open(x.c_str()+1, ios::app);
217  if (!logfile.good())
218  {
219  // Redirect to the previous logfile (or cout if that's not possible)
220  if (logfile.is_open()) logfile.close();
221  logfile.open(logfilename.c_str(), ios::app);
222  logger.rdbuf(logfile.is_open() ? logfile.rdbuf() : cout.rdbuf());
223  // The log file could not be opened
224  throw RuntimeException("Could not open log file '" + x + "'");
225  }
226 
227  // Store the file name
228  logfilename = x;
229 
230  // Redirect the log file.
231  logger.rdbuf(logfile.rdbuf());
232 
233  // Print a nice header
234  logger << "Start logging frePPLe " << PACKAGE_VERSION << " ("
235  << __DATE__ << ") at " << Date::now() << endl;
236 }
237 
238 
240 {
241  // Type definition of the initialization function
242  typedef const char* (*func)(const ParameterList&);
243 
244  // Validate
245  if (lib.empty())
246  throw DataException("Error: No library name specified for loading");
247 
248 #ifdef WIN32
249  // Load the library - The windows way
250 
251  // Change the error mode: we handle errors now, not the operating system
252  UINT em = SetErrorMode(SEM_FAILCRITICALERRORS);
253  HINSTANCE handle = LoadLibraryEx(lib.c_str(),NULL,LOAD_WITH_ALTERED_SEARCH_PATH);
254  if (!handle) handle = LoadLibraryEx(lib.c_str(), NULL, 0);
255  if (!handle)
256  {
257  // Get the error description
258  char error[256];
259  FormatMessage(
260  FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
261  NULL,
262  GetLastError(),
263  0,
264  error,
265  256,
266  NULL );
267  throw RuntimeException(error);
268  }
269  SetErrorMode(em); // Restore the previous error mode
270 
271  // Find the initialization routine
272  func inithandle =
273  reinterpret_cast<func>(GetProcAddress(HMODULE(handle), "initialize"));
274  if (!inithandle)
275  {
276  // Get the error description
277  char error[256];
278  FormatMessage(
279  FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
280  NULL,
281  GetLastError(),
282  0,
283  error,
284  256,
285  NULL );
286  throw RuntimeException(error);
287  }
288 
289 #else
290  // Load the library - The UNIX way
291 
292  // Search the frePPLe directories for the library
293  string fullpath = Environment::searchFile(lib);
294  if (fullpath.empty())
295  throw RuntimeException("Module '" + lib + "' not found");
296  dlerror(); // Clear the previous error
297  void *handle = dlopen(fullpath.c_str(), RTLD_NOW | RTLD_GLOBAL);
298  const char *err = dlerror(); // Pick up the error string
299  if (err)
300  {
301  // Search the normal path for the library
302  dlerror(); // Clear the previous error
303  handle = dlopen(lib.c_str(), RTLD_NOW | RTLD_GLOBAL);
304  err = dlerror(); // Pick up the error string
305  if (err) throw RuntimeException(err);
306  }
307 
308  // Find the initialization routine
309  func inithandle = (func)(dlsym(handle, "initialize"));
310  err = dlerror(); // Pick up the error string
311  if (err) throw RuntimeException(err);
312 #endif
313 
314  // Call the initialization routine with the parameter list
315  string x = (inithandle)(parameters);
316  if (x.empty()) throw DataException("Invalid module");
317 
318  // Insert the new module in the registry
319  moduleRegistry.insert(x);
320 }
321 
322 
323 DECLARE_EXPORT void MetaClass::registerClass (const string& a, const string& b,
324  bool def, creatorDefault f)
325 {
326  // Find or create the category
327  MetaCategory* cat
328  = const_cast<MetaCategory*>(MetaCategory::findCategoryByTag(a.c_str()));
329 
330  // Check for a valid category
331  if (!cat)
332  throw LogicException("Category " + a
333  + " not found when registering class " + b);
334 
335  // Update fields
336  type = b.empty() ? "unspecified" : b;
337  typetag = &Keyword::find(type.c_str());
338  category = cat;
339 
340  // Update the metadata table
341  cat->classes[Keyword::hash(b)] = this;
342 
343  // Register this tag also as the default one, if requested
344  if (def) cat->classes[Keyword::hash("default")] = this;
345 
346  // Set method pointers to NULL
348 }
349 
350 
351 DECLARE_EXPORT MetaCategory::MetaCategory (const string& a, const string& gr,
352  readController f, writeController w)
353 {
354  // Update registry
355  if (!a.empty()) categoriesByTag[Keyword::hash(a)] = this;
356  if (!gr.empty()) categoriesByGroupTag[Keyword::hash(gr)] = this;
357 
358  // Update fields
359  readFunction = f;
360  writeFunction = w;
361  type = a.empty() ? "unspecified" : a;
362  typetag = &Keyword::find(type.c_str());
363  group = gr.empty() ? "unspecified" : gr;
364  grouptag = &Keyword::find(group.c_str());
365 
366  // Maintain a linked list of all registered categories
367  nextCategory = NULL;
368  if (!firstCategory)
369  firstCategory = this;
370  else
371  {
372  const MetaCategory *i = firstCategory;
373  while (i->nextCategory) i = i->nextCategory;
374  const_cast<MetaCategory*>(i)->nextCategory = this;
375  }
376 }
377 
378 
380 {
381  // Loop through all categories
382  CategoryMap::const_iterator i = categoriesByTag.find(Keyword::hash(c));
383  return (i!=categoriesByTag.end()) ? i->second : NULL;
384 }
385 
386 
388 {
389  // Loop through all categories
390  CategoryMap::const_iterator i = categoriesByTag.find(h);
391  return (i!=categoriesByTag.end()) ? i->second : NULL;
392 }
393 
394 
396 {
397  // Loop through all categories
398  CategoryMap::const_iterator i = categoriesByGroupTag.find(Keyword::hash(c));
399  return (i!=categoriesByGroupTag.end()) ? i->second : NULL;
400 }
401 
402 
404 {
405  // Loop through all categories
406  CategoryMap::const_iterator i = categoriesByGroupTag.find(h);
407  return (i!=categoriesByGroupTag.end()) ? i->second : NULL;
408 }
409 
410 
412 {
413  // Look up in the registered classes
414  MetaCategory::ClassMap::const_iterator j = classes.find(Keyword::hash(c));
415  return (j == classes.end()) ? NULL : j->second;
416 }
417 
418 
420 {
421  // Look up in the registered classes
422  MetaCategory::ClassMap::const_iterator j = classes.find(h);
423  return (j == classes.end()) ? NULL : j->second;
424 }
425 
426 
428 {
429  for (const MetaCategory *i = firstCategory; i; i = i->nextCategory)
430  if (i->writeFunction) i->writeFunction(i, o);
431 }
432 
433 
435 {
436  // Loop through all categories
437  for (MetaCategory::CategoryMap::const_iterator i = MetaCategory::categoriesByTag.begin();
438  i != MetaCategory::categoriesByTag.end(); ++i)
439  {
440  // Look up in the registered classes
441  MetaCategory::ClassMap::const_iterator j
442  = i->second->classes.find(Keyword::hash(c));
443  if (j != i->second->classes.end()) return j->second;
444  }
445  // Not found...
446  return NULL;
447 }
448 
449 
451 {
452  logger << "Registered classes:" << endl;
453  // Loop through all categories
454  for (MetaCategory::CategoryMap::const_iterator i = MetaCategory::categoriesByTag.begin();
455  i != MetaCategory::categoriesByTag.end(); ++i)
456  {
457  logger << " " << i->second->type << endl;
458  // Loop through the classes for the category
459  for (MetaCategory::ClassMap::const_iterator
460  j = i->second->classes.begin();
461  j != i->second->classes.end();
462  ++j)
463  if (j->first == Keyword::hash("default"))
464  logger << " default ( = " << j->second->type << " )" << j->second << endl;
465  else
466  logger << " " << j->second->type << j->second << endl;
467  }
468 }
469 
470 
472 {
473  // Validate the action
474  if (!x) throw LogicException("Invalid action NULL");
475  else if (!strcmp(x,"AC")) return ADD_CHANGE;
476  else if (!strcmp(x,"A")) return ADD;
477  else if (!strcmp(x,"C")) return CHANGE;
478  else if (!strcmp(x,"R")) return REMOVE;
479  else throw LogicException("Invalid action '" + string(x) + "'");
480 }
481 
482 
484 {
485  // Decode the string and return the default in the absence of the attribute
486  const DataElement* c = atts.get(Tags::tag_action);
487  return *c ? decodeAction(c->getString().c_str()) : ADD_CHANGE;
488 }
489 
490 
492 {
493  bool result(true);
494  for (list<Functor*>::const_iterator i = subscribers[a].begin();
495  i != subscribers[a].end(); ++i)
496  // Note that we always call all subscribers, even if one or more
497  // already replied negatively. However, an exception thrown from a
498  // callback method will break the publishing chain.
499  if (!(*i)->callback(v,a)) result = false;
500 
501  // Raise the event also on the category, if there is a valid one
502  return (category && category!=this) ?
503  (result && category->raiseEvent(v,a)) :
504  result;
505 }
506 
507 
509 {
510  Action act = ADD;
511  switch (act)
512  {
513  case REMOVE:
514  throw DataException
515  ("Entity " + cat->type + " doesn't support REMOVE action");
516  case CHANGE:
517  throw DataException
518  ("Entity " + cat->type + " doesn't support CHANGE action");
519  default:
520  /* Lookup for the class in the map of registered classes. */
521  const MetaClass* j;
522  if (cat->category)
523  // Class metadata passed: we already know what type to create
524  j = cat;
525  else
526  {
527  // Category metadata passed: we need to look up the type
528  const DataElement* type = in.get(Tags::tag_type);
529  j = static_cast<const MetaCategory&>(*cat).findClass(*type ? Keyword::hash(type->getString()) : MetaCategory::defaultHash);
530  if (!j)
531  {
532  string t(*type ? type->getString() : "default");
533  throw LogicException("No type " + t + " registered for category " + cat->type);
534  }
535  }
536 
537  // Call the factory method
538  Object* result = j->factoryMethodDefault();
539 
540  // Run the callback methods
541  if (!result->getType().raiseEvent(result, SIG_ADD))
542  {
543  // Creation denied
544  delete result;
545  throw DataException("Can't create object");
546  }
547 
548  // Creation accepted
549  return result;
550  }
551  throw LogicException("Unreachable code reached");
552  return NULL;
553 }
554 
555 
557 {
558  // Note that this function is never called on its own. It is always called
559  // from the writeElement() method of a subclass.
560  // Hence, we don't bother about the mode.
564 }
565 
566 
567 void HasDescription::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
568 {
569  if (pAttr.isA(Tags::tag_category))
570  setCategory(pElement.getString());
571  else if (pAttr.isA(Tags::tag_subcategory))
572  setSubCategory(pElement.getString());
573  else if (pAttr.isA(Tags::tag_description))
574  setDescription(pElement.getString());
575 }
576 
577 
578 DECLARE_EXPORT bool matchWildcard(const char* wild, const char *str)
579 {
580  // Empty arguments: always return a match
581  if (!wild || !str) return 1;
582 
583  const char *cp = NULL, *mp = NULL;
584 
585  while ((*str) && *wild != '*')
586  {
587  if (*wild != *str && *wild != '?')
588  // Does not match
589  return 0;
590  wild++;
591  str++;
592  }
593 
594  while (*str)
595  {
596  if (*wild == '*')
597  {
598  if (!*++wild) return 1;
599  mp = wild;
600  cp = str+1;
601  }
602  else if (*wild == *str || *wild == '?')
603  {
604  wild++;
605  str++;
606  }
607  else
608  {
609  wild = mp;
610  str = cp++;
611  }
612  }
613 
614  while (*wild == '*') wild++;
615  return !*wild;
616 }
617 
618 } // end namespace
619 } // end namespace
620