utils/library.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2007-2012 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 
69 void LibraryUtils::initialize(int argc, char *argv[])
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 += filename;
153  result = stat(fullname.c_str(), &stat_p);
154  if (!result && (stat_p.st_mode & S_IREAD))
155  return fullname;
156 #endif
157 
158  // Not found
159  return "";
160 }
161 
162 
164 {
165  // Previously detected already
166  if (processorcores >= 1) return processorcores;
167 
168  // Detect the number of cores on the machine
169 #ifdef WIN32
170  // Windows
171  SYSTEM_INFO sysinfo;
172  GetSystemInfo(&sysinfo);
173  processorcores = sysinfo.dwNumberOfProcessors;
174 #else
175  // Linux, Solaris and AIX.
176  // Tough luck for other platforms.
177  processorcores = sysconf(_SC_NPROCESSORS_ONLN);
178 #endif
179  // Detection failed...
180  if (processorcores<1) processorcores = 1;
181  return processorcores;
182 }
183 
184 
186 {
187  // Bye bye message
188  if (!logfilename.empty())
189  logger << "Stop logging at " << Date::now() << endl;
190 
191  // Close an eventual existing log file.
192  if (logfile.is_open()) logfile.close();
193 
194  // No new logfile specified: redirect to the standard output stream
195  if (x.empty() || x == "+")
196  {
197  logfilename = x;
198  logger.rdbuf(cout.rdbuf());
199  return;
200  }
201 
202  // Open the file: either as a new file, either appending to existing file
203  if (x[0] != '+') logfile.open(x.c_str(), ios::out);
204  else logfile.open(x.c_str()+1, ios::app);
205  if (!logfile.good())
206  {
207  // Redirect to the previous logfile (or cout if that's not possible)
208  if (logfile.is_open()) logfile.close();
209  logfile.open(logfilename.c_str(), ios::app);
210  logger.rdbuf(logfile.is_open() ? logfile.rdbuf() : cout.rdbuf());
211  // The log file could not be opened
212  throw RuntimeException("Could not open log file '" + x + "'");
213  }
214 
215  // Store the file name
216  logfilename = x;
217 
218  // Redirect the log file.
219  logger.rdbuf(logfile.rdbuf());
220 
221  // Print a nice header
222  logger << "Start logging frePPLe " << PACKAGE_VERSION << " ("
223  << __DATE__ << ") at " << Date::now() << endl;
224 }
225 
226 
228 {
229  // Type definition of the initialization function
230  typedef const char* (*func)(const ParameterList&);
231 
232  // Validate
233  if (lib.empty())
234  throw DataException("Error: No library name specified for loading");
235 
236 #ifdef WIN32
237  // Load the library - The windows way
238 
239  // Change the error mode: we handle errors now, not the operating system
240  UINT em = SetErrorMode(SEM_FAILCRITICALERRORS);
241  HINSTANCE handle = LoadLibraryEx(lib.c_str(),NULL,LOAD_WITH_ALTERED_SEARCH_PATH);
242  if (!handle) handle = LoadLibraryEx(lib.c_str(), NULL, 0);
243  if (!handle)
244  {
245  // Get the error description
246  char error[256];
247  FormatMessage(
248  FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
249  NULL,
250  GetLastError(),
251  0,
252  error,
253  256,
254  NULL );
255  throw RuntimeException(error);
256  }
257  SetErrorMode(em); // Restore the previous error mode
258 
259  // Find the initialization routine
260  func inithandle =
261  reinterpret_cast<func>(GetProcAddress(HMODULE(handle), "initialize"));
262  if (!inithandle)
263  {
264  // Get the error description
265  char error[256];
266  FormatMessage(
267  FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
268  NULL,
269  GetLastError(),
270  0,
271  error,
272  256,
273  NULL );
274  throw RuntimeException(error);
275  }
276 
277 #else
278  // Load the library - The UNIX way
279 
280  // Search the frePPLe directories for the library
281  string fullpath = Environment::searchFile(lib);
282  if (fullpath.empty())
283  throw RuntimeException("Module '" + lib + "' not found");
284  dlerror(); // Clear the previous error
285  void *handle = dlopen(fullpath.c_str(), RTLD_NOW | RTLD_GLOBAL);
286  const char *err = dlerror(); // Pick up the error string
287  if (err)
288  {
289  // Search the normal path for the library
290  dlerror(); // Clear the previous error
291  handle = dlopen(lib.c_str(), RTLD_NOW | RTLD_GLOBAL);
292  err = dlerror(); // Pick up the error string
293  if (err) throw RuntimeException(err);
294  }
295 
296  // Find the initialization routine
297  func inithandle = (func)(dlsym(handle, "initialize"));
298  err = dlerror(); // Pick up the error string
299  if (err) throw RuntimeException(err);
300 #endif
301 
302  // Call the initialization routine with the parameter list
303  string x = (inithandle)(parameters);
304  if (x.empty()) throw DataException("Invalid module name returned");
305 
306  // Insert the new module in the registry
307  moduleRegistry.insert(x);
308 }
309 
310 
311 DECLARE_EXPORT void MetaClass::registerClass (const string& a, const string& b,
312  bool def, creatorDefault f)
313 {
314  // Find or create the category
315  MetaCategory* cat
316  = const_cast<MetaCategory*>(MetaCategory::findCategoryByTag(a.c_str()));
317 
318  // Check for a valid category
319  if (!cat)
320  throw LogicException("Category " + a
321  + " not found when registering class " + b);
322 
323  // Update fields
324  type = b.empty() ? "unspecified" : b;
325  typetag = &Keyword::find(type.c_str());
326  category = cat;
327 
328  // Update the metadata table
329  cat->classes[Keyword::hash(b)] = this;
330 
331  // Register this tag also as the default one, if requested
332  if (def) cat->classes[Keyword::hash("default")] = this;
333 
334  // Set method pointers to NULL
336 }
337 
338 
339 DECLARE_EXPORT MetaCategory::MetaCategory (const string& a, const string& gr,
340  readController f, writeController w)
341 {
342  // Update registry
343  if (!a.empty()) categoriesByTag[Keyword::hash(a)] = this;
344  if (!gr.empty()) categoriesByGroupTag[Keyword::hash(gr)] = this;
345 
346  // Update fields
347  readFunction = f;
348  writeFunction = w;
349  type = a.empty() ? "unspecified" : a;
350  typetag = &Keyword::find(type.c_str());
351  group = gr.empty() ? "unspecified" : gr;
352  grouptag = &Keyword::find(group.c_str());
353 
354  // Maintain a linked list of all registered categories
355  nextCategory = NULL;
356  if (!firstCategory)
357  firstCategory = this;
358  else
359  {
360  const MetaCategory *i = firstCategory;
361  while (i->nextCategory) i = i->nextCategory;
362  const_cast<MetaCategory*>(i)->nextCategory = this;
363  }
364 }
365 
366 
368 {
369  // Loop through all categories
370  CategoryMap::const_iterator i = categoriesByTag.find(Keyword::hash(c));
371  return (i!=categoriesByTag.end()) ? i->second : NULL;
372 }
373 
374 
376 {
377  // Loop through all categories
378  CategoryMap::const_iterator i = categoriesByTag.find(h);
379  return (i!=categoriesByTag.end()) ? i->second : NULL;
380 }
381 
382 
384 {
385  // Loop through all categories
386  CategoryMap::const_iterator i = categoriesByGroupTag.find(Keyword::hash(c));
387  return (i!=categoriesByGroupTag.end()) ? i->second : NULL;
388 }
389 
390 
392 {
393  // Loop through all categories
394  CategoryMap::const_iterator i = categoriesByGroupTag.find(h);
395  return (i!=categoriesByGroupTag.end()) ? i->second : NULL;
396 }
397 
398 
400 {
401  // Look up in the registered classes
402  MetaCategory::ClassMap::const_iterator j = classes.find(Keyword::hash(c));
403  return (j == classes.end()) ? NULL : j->second;
404 }
405 
406 
408 {
409  // Look up in the registered classes
410  MetaCategory::ClassMap::const_iterator j = classes.find(h);
411  return (j == classes.end()) ? NULL : j->second;
412 }
413 
414 
416 {
417  for (const MetaCategory *i = firstCategory; i; i = i->nextCategory)
418  if (i->writeFunction) i->writeFunction(i, o);
419 }
420 
421 
423 {
424  // Loop through all categories
425  for (MetaCategory::CategoryMap::const_iterator i = MetaCategory::categoriesByTag.begin();
426  i != MetaCategory::categoriesByTag.end(); ++i)
427  {
428  // Look up in the registered classes
429  MetaCategory::ClassMap::const_iterator j
430  = i->second->classes.find(Keyword::hash(c));
431  if (j != i->second->classes.end()) return j->second;
432  }
433  // Not found...
434  return NULL;
435 }
436 
437 
439 {
440  logger << "Registered classes:" << endl;
441  // Loop through all categories
442  for (MetaCategory::CategoryMap::const_iterator i = MetaCategory::categoriesByTag.begin();
443  i != MetaCategory::categoriesByTag.end(); ++i)
444  {
445  logger << " " << i->second->type << endl;
446  // Loop through the classes for the category
447  for (MetaCategory::ClassMap::const_iterator
448  j = i->second->classes.begin();
449  j != i->second->classes.end();
450  ++j)
451  if (j->first == Keyword::hash("default"))
452  logger << " default ( = " << j->second->type << " )" << j->second << endl;
453  else
454  logger << " " << j->second->type << j->second << endl;
455  }
456 }
457 
458 
460 {
461  // Validate the action
462  if (!x) throw LogicException("Invalid action NULL");
463  else if (!strcmp(x,"AC")) return ADD_CHANGE;
464  else if (!strcmp(x,"A")) return ADD;
465  else if (!strcmp(x,"C")) return CHANGE;
466  else if (!strcmp(x,"R")) return REMOVE;
467  else throw LogicException("Invalid action '" + string(x) + "'");
468 }
469 
470 
472 {
473  // Decode the string and return the default in the absence of the attribute
474  const DataElement* c = atts.get(Tags::tag_action);
475  return *c ? decodeAction(c->getString().c_str()) : ADD_CHANGE;
476 }
477 
478 
480 {
481  bool result(true);
482  for (list<Functor*>::const_iterator i = subscribers[a].begin();
483  i != subscribers[a].end(); ++i)
484  // Note that we always call all subscribers, even if one or more
485  // already replied negatively. However, an exception thrown from a
486  // callback method will break the publishing chain.
487  if (!(*i)->callback(v,a)) result = false;
488 
489  // Raise the event also on the category, if there is a valid one
490  return (category && category!=this) ?
491  (result && category->raiseEvent(v,a)) :
492  result;
493 }
494 
495 
497 {
498  Action act = ADD;
499  switch (act)
500  {
501  case REMOVE:
502  throw DataException
503  ("Entity " + cat->type + " doesn't support REMOVE action");
504  case CHANGE:
505  throw DataException
506  ("Entity " + cat->type + " doesn't support CHANGE action");
507  default:
508  /* Lookup for the class in the map of registered classes. */
509  const MetaClass* j;
510  if (cat->category)
511  // Class metadata passed: we already know what type to create
512  j = cat;
513  else
514  {
515  // Category metadata passed: we need to look up the type
516  const DataElement* type = in.get(Tags::tag_type);
517  j = static_cast<const MetaCategory&>(*cat).findClass(*type ? Keyword::hash(type->getString()) : MetaCategory::defaultHash);
518  if (!j)
519  {
520  string t(*type ? type->getString() : "default");
521  throw LogicException("No type " + t + " registered for category " + cat->type);
522  }
523  }
524 
525  // Call the factory method
526  Object* result = j->factoryMethodDefault();
527 
528  // Run the callback methods
529  if (!result->getType().raiseEvent(result, SIG_ADD))
530  {
531  // Creation denied
532  delete result;
533  throw DataException("Can't create object");
534  }
535 
536  // Creation accepted
537  return result;
538  }
539  throw LogicException("Unreachable code reached");
540  return NULL;
541 }
542 
543 
545 {
546  // Note that this function is never called on its own. It is always called
547  // from the writeElement() method of a subclass.
548  // Hence, we don't bother about the mode.
552 }
553 
554 
555 void HasDescription::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
556 {
557  if (pAttr.isA(Tags::tag_category))
558  setCategory(pElement.getString());
559  else if (pAttr.isA(Tags::tag_subcategory))
560  setSubCategory(pElement.getString());
561  else if (pAttr.isA(Tags::tag_description))
562  setDescription(pElement.getString());
563 }
564 
565 
566 DECLARE_EXPORT bool matchWildcard(const char* wild, const char *str)
567 {
568  // Empty arguments: always return a match
569  if (!wild || !str) return 1;
570 
571  const char *cp = NULL, *mp = NULL;
572 
573  while ((*str) && *wild != '*')
574  {
575  if (*wild != *str && *wild != '?')
576  // Does not match
577  return 0;
578  wild++;
579  str++;
580  }
581 
582  while (*str)
583  {
584  if (*wild == '*')
585  {
586  if (!*++wild) return 1;
587  mp = wild;
588  cp = str+1;
589  }
590  else if (*wild == *str || *wild == '?')
591  {
592  wild++;
593  str++;
594  }
595  else
596  {
597  wild = mp;
598  str = cp++;
599  }
600  }
601 
602  while (*wild == '*') wild++;
603  return !*wild;
604 }
605 
606 } // end namespace
607 } // end namespace
608