api.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. #
  2. # Metrix++, Copyright 2009-2013, Metrix++ Project
  3. # Link: http://metrixplusplus.sourceforge.net
  4. #
  5. # This file is a part of Metrix++ Tool.
  6. #
  7. # Metrix++ is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, version 3 of the License.
  10. #
  11. # Metrix++ is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with Metrix++. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. import logging
  20. class Data(object):
  21. def __init__(self):
  22. self.data = {}
  23. def get_data(self, namespace, field):
  24. if namespace not in self.data.keys():
  25. return None
  26. if field not in self.data[namespace].keys():
  27. return None
  28. return self.data[namespace][field]
  29. def set_data(self, namespace, field, value):
  30. if namespace not in self.data:
  31. self.data[namespace] = {}
  32. self.data[namespace][field] = value
  33. def iterate_namespaces(self):
  34. for namespace in self.data.keys():
  35. yield namespace
  36. def iterate_fields(self, namespace):
  37. for field in self.data[namespace].keys():
  38. yield (field, self.data[namespace][field])
  39. def get_data_tree(self, namespaces=None):
  40. return self.data
  41. def __repr__(self):
  42. return object.__repr__(self) + " with data " + self.data.__repr__()
  43. class LoadableData(Data):
  44. def __init__(self, loader, file_id, region_id):
  45. Data.__init__(self)
  46. self.loader = loader
  47. self.file_id = file_id
  48. self.region_id = region_id
  49. self.loaded_namespaces = []
  50. self.changed_namespaces = []
  51. def load_namespace(self, namespace):
  52. try:
  53. row = self.loader.db.get_row(namespace, self.file_id, self.region_id)
  54. except Exception:
  55. logging.debug("No data in the database for namespace: " + namespace)
  56. return
  57. if row == None:
  58. return
  59. for column_name in row.keys():
  60. packager = self.loader.get_namespace(namespace).get_field_packager(column_name)
  61. if packager == None:
  62. continue
  63. if row[column_name] == None:
  64. continue
  65. Data.set_data(self, namespace, column_name, packager.unpack(row[column_name]))
  66. def set_data(self, namespace, field, value):
  67. if namespace not in self.changed_namespaces:
  68. self.changed_namespaces.append(namespace)
  69. return Data.set_data(self, namespace, field, value)
  70. def get_data(self, namespace, field):
  71. if namespace not in self.loaded_namespaces:
  72. self.loaded_namespaces.append(namespace)
  73. self.load_namespace(namespace)
  74. return Data.get_data(self, namespace, field)
  75. def is_namespace_updated(self, namespace):
  76. return namespace in self.changed_namespaces
  77. def is_namespace_loaded(self, namespace):
  78. return namespace in self.loaded_namespaces
  79. def get_data_tree(self, namespaces=None):
  80. if namespaces == None:
  81. namespaces = self.loader.iterate_namespace_names()
  82. for each in namespaces:
  83. self.load_namespace(each)
  84. return Data.get_data_tree(self)
  85. class FileRegionData(LoadableData):
  86. class T(object):
  87. NONE = 0x00
  88. GLOBAL = 0x01
  89. CLASS = 0x02
  90. STRUCT = 0x04
  91. NAMESPACE = 0x08
  92. FUNCTION = 0x10
  93. INTERFACE = 0x20
  94. ANY = 0xFF
  95. def to_str(self, group):
  96. if group == self.NONE:
  97. return "none"
  98. elif group == self.GLOBAL:
  99. return "global"
  100. elif group == self.CLASS:
  101. return "class"
  102. elif group == self.STRUCT:
  103. return "struct"
  104. elif group == self.NAMESPACE:
  105. return "namespace"
  106. elif group == self.FUNCTION:
  107. return "function"
  108. elif group == self.INTERFACE:
  109. return "interface"
  110. else:
  111. assert(False)
  112. def __init__(self, loader, file_id, region_id, region_name, offset_begin, offset_end, line_begin, line_end, cursor_line, group, checksum):
  113. LoadableData.__init__(self, loader, file_id, region_id)
  114. self.name = region_name
  115. self.begin = offset_begin
  116. self.end = offset_end
  117. self.line_begin = line_begin
  118. self.line_end = line_end
  119. self.cursor = cursor_line
  120. self.group = group
  121. self.checksum = checksum
  122. self.children = []
  123. def get_id(self):
  124. return self.region_id
  125. def get_name(self):
  126. return self.name
  127. def get_offset_begin(self):
  128. return self.begin
  129. def get_offset_end(self):
  130. return self.end
  131. def get_line_begin(self):
  132. return self.line_begin
  133. def get_line_end(self):
  134. return self.line_end
  135. def get_cursor(self):
  136. return self.cursor
  137. def get_type(self):
  138. return self.group
  139. def get_checksum(self):
  140. return self.checksum
  141. def register_subregion_id(self, child_id):
  142. self.children.append(child_id)
  143. def iterate_subregion_ids(self):
  144. return self.children
  145. class Marker(object):
  146. class T(object):
  147. NONE = 0x00
  148. COMMENT = 0x01
  149. STRING = 0x02
  150. PREPROCESSOR = 0x04
  151. CODE = 0x08
  152. ALL_EXCEPT_CODE = 0x07
  153. ANY = 0xFF
  154. def to_str(self, group):
  155. if group == self.NONE:
  156. return "none"
  157. elif group == self.COMMENT:
  158. return "comment"
  159. elif group == self.STRING:
  160. return "string"
  161. elif group == self.PREPROCESSOR:
  162. return "preprocessor"
  163. elif group == self.CODE:
  164. return "code"
  165. else:
  166. assert(False)
  167. def __init__(self, offset_begin, offset_end, group):
  168. self.begin = offset_begin
  169. self.end = offset_end
  170. self.group = group
  171. def get_offset_begin(self):
  172. return self.begin
  173. def get_offset_end(self):
  174. return self.end
  175. def get_type(self):
  176. return self.group
  177. class FileData(LoadableData):
  178. def __init__(self, loader, path, file_id, checksum, content):
  179. LoadableData.__init__(self, loader, file_id, None)
  180. self.path = path
  181. self.checksum = checksum
  182. self.content = content
  183. self.regions = None
  184. self.markers = None
  185. self.loader = loader
  186. self.loading_tmp = []
  187. def get_id(self):
  188. return self.file_id
  189. def get_path(self):
  190. return self.path
  191. def get_checksum(self):
  192. return self.checksum
  193. def get_content(self):
  194. return self.content
  195. def internal_append_region(self, region):
  196. # here we apply some magic - we rely on special ordering of coming regions,
  197. # which is supported by code parsers
  198. prev_id = None
  199. while True:
  200. if len(self.loading_tmp) == 0:
  201. break
  202. prev_id = self.loading_tmp.pop()
  203. if self.get_region(prev_id).get_offset_end() > region.get_offset_begin():
  204. self.loading_tmp.append(prev_id) # return back
  205. break
  206. self.loading_tmp.append(region.get_id())
  207. if prev_id != None:
  208. self.get_region(prev_id).register_subregion_id(region.get_id())
  209. self.regions.append(region)
  210. def load_regions(self):
  211. if self.regions == None:
  212. self.regions = []
  213. for each in self.loader.db.iterate_regions(self.get_id()):
  214. self.internal_append_region(FileRegionData(self.loader,
  215. self.get_id(),
  216. each.region_id,
  217. each.name,
  218. each.begin,
  219. each.end,
  220. each.line_begin,
  221. each.line_end,
  222. each.cursor,
  223. each.group,
  224. each.checksum))
  225. assert(len(self.regions) == each.region_id)
  226. def add_region(self, region_name, offset_begin, offset_end, line_begin, line_end, cursor_line, group, checksum):
  227. if self.regions == None:
  228. self.regions = [] # do not load in time of collection
  229. new_id = len(self.regions) + 1
  230. self.internal_append_region(FileRegionData(self.loader, self.get_id(), new_id, region_name, offset_begin, offset_end, line_begin, line_end, cursor_line, group, checksum))
  231. self.loader.db.create_region(self.file_id, new_id, region_name, offset_begin, offset_end, line_begin, line_end, cursor_line, group, checksum)
  232. return new_id
  233. def get_region(self, region_id):
  234. self.load_regions()
  235. return self.regions[region_id - 1]
  236. def get_region_types(self):
  237. return FileRegionData.T
  238. def iterate_regions(self, filter_group = FileRegionData.T.ANY):
  239. self.load_regions()
  240. for each in self.regions:
  241. if each.group & filter_group:
  242. yield each
  243. def are_regions_loaded(self):
  244. return self.regions != None
  245. def load_markers(self):
  246. if self.markers == None:
  247. self.markers = []
  248. for each in self.loader.db.iterate_markers(self.get_id()):
  249. self.markers.append(Marker(each.begin, each.end, each.group))
  250. def add_marker(self, offset_begin, offset_end, group):
  251. if self.markers == None:
  252. self.markers = [] # do not load in time of collection
  253. self.markers.append(Marker(offset_begin, offset_end, group))
  254. self.loader.db.create_marker(self.file_id, offset_begin, offset_end, group)
  255. def iterate_markers(self, filter_group = Marker.T.COMMENT |
  256. Marker.T.STRING | Marker.T.PREPROCESSOR,
  257. region_id = None, exclude_children = True, merge = False):
  258. self.load_markers()
  259. # merged markers
  260. if merge == True:
  261. next_marker = None
  262. for marker in self.iterate_markers(filter_group, region_id, exclude_children, merge = False):
  263. if next_marker != None:
  264. if next_marker.get_offset_end() == marker.get_offset_begin():
  265. # sequential markers
  266. next_marker = Marker(next_marker.get_offset_begin(),
  267. marker.get_offset_end(),
  268. next_marker.get_type() | marker.get_type())
  269. else:
  270. yield next_marker
  271. next_marker = None
  272. if next_marker == None:
  273. next_marker = Marker(marker.get_offset_begin(),
  274. marker.get_offset_end(),
  275. marker.get_type())
  276. if next_marker != None:
  277. yield next_marker
  278. # all markers per file
  279. elif region_id == None:
  280. next_code_marker_start = 0
  281. for marker in self.markers:
  282. if Marker.T.CODE & filter_group and next_code_marker_start < marker.get_offset_begin():
  283. yield Marker(next_code_marker_start, marker.get_offset_begin(), Marker.T.CODE)
  284. if marker.group & filter_group:
  285. yield marker
  286. next_code_marker_start = marker.get_offset_end()
  287. if Marker.T.CODE & filter_group and next_code_marker_start < len(self.get_content()):
  288. yield Marker(next_code_marker_start, len(self.get_content()), Marker.T.CODE)
  289. # markers per region
  290. else:
  291. region = self.get_region(region_id)
  292. if region != None:
  293. # code parsers and database know about non-code markers
  294. # clients want to iterate code as markers as well
  295. # so, we embed code markers in run-time
  296. class CodeMarker(Marker):
  297. pass
  298. # cache markers for all regions if it does not exist
  299. if hasattr(region, '_markers_list') == False:
  300. # subroutine to populate _markers_list attribute
  301. # _markers_list does include code markers
  302. def cache_markers_list_rec(data, region_id, marker_start_ind, next_code_marker_start):
  303. region = data.get_region(region_id)
  304. region._markers_list = []
  305. region._first_marker_ind = marker_start_ind
  306. #next_code_marker_start = region.get_offset_begin()
  307. for sub_id in region.iterate_subregion_ids():
  308. subregion = data.get_region(sub_id)
  309. # cache all markers before the subregion
  310. while len(data.markers) > marker_start_ind and \
  311. subregion.get_offset_begin() > data.markers[marker_start_ind].get_offset_begin():
  312. if next_code_marker_start < data.markers[marker_start_ind].get_offset_begin():
  313. # append code markers coming before non-code marker
  314. region._markers_list.append(CodeMarker(next_code_marker_start,
  315. data.markers[marker_start_ind].get_offset_begin(),
  316. Marker.T.CODE))
  317. next_code_marker_start = data.markers[marker_start_ind].get_offset_end()
  318. region._markers_list.append(marker_start_ind)
  319. marker_start_ind += 1
  320. # cache all code markers before the subregion but after the last marker
  321. if next_code_marker_start < subregion.get_offset_begin():
  322. region._markers_list.append(CodeMarker(next_code_marker_start,
  323. subregion.get_offset_begin(),
  324. Marker.T.CODE))
  325. next_code_marker_start = subregion.get_offset_begin()
  326. # here is the recursive call for all sub-regions
  327. (marker_start_ind, next_code_marker_start) = cache_markers_list_rec(data,
  328. sub_id,
  329. marker_start_ind,
  330. next_code_marker_start)
  331. # cache all markers after the last subregion
  332. while len(data.markers) > marker_start_ind and \
  333. region.get_offset_end() > data.markers[marker_start_ind].get_offset_begin():
  334. # append code markers coming before non-code marker
  335. if next_code_marker_start < data.markers[marker_start_ind].get_offset_begin():
  336. region._markers_list.append(CodeMarker(next_code_marker_start,
  337. data.markers[marker_start_ind].get_offset_begin(),
  338. Marker.T.CODE))
  339. next_code_marker_start = data.markers[marker_start_ind].get_offset_end()
  340. region._markers_list.append(marker_start_ind)
  341. marker_start_ind += 1
  342. # cache the last code segment after the last marker
  343. if next_code_marker_start < region.get_offset_end():
  344. region._markers_list.append(CodeMarker(next_code_marker_start,
  345. region.get_offset_end(),
  346. Marker.T.CODE))
  347. next_code_marker_start = region.get_offset_end()
  348. # return the starting point for the next call of this function
  349. return (marker_start_ind, next_code_marker_start)
  350. # append markers list to all regions recursively
  351. (next_marker_pos, next_code_marker_start) = cache_markers_list_rec(self, 1, 0, 0)
  352. assert(next_marker_pos == len(self.markers))
  353. # excluding subregions
  354. if exclude_children == True:
  355. for marker_ind in region._markers_list:
  356. if isinstance(marker_ind, int):
  357. marker = self.markers[marker_ind]
  358. else:
  359. marker = marker_ind # CodeMarker
  360. if marker.group & filter_group:
  361. yield marker
  362. # including subregions
  363. else:
  364. next_code_marker_start = region.get_offset_begin()
  365. for marker in self.markers[region._first_marker_ind:]:
  366. if marker.get_offset_begin() >= region.get_offset_end():
  367. break
  368. if region.get_offset_begin() > marker.get_offset_begin():
  369. continue
  370. if Marker.T.CODE & filter_group and next_code_marker_start < marker.get_offset_begin():
  371. yield Marker(next_code_marker_start, marker.get_offset_begin(), Marker.T.CODE)
  372. if marker.group & filter_group:
  373. yield marker
  374. next_code_marker_start = marker.get_offset_end()
  375. if Marker.T.CODE & filter_group and next_code_marker_start < region.get_offset_end():
  376. yield Marker(next_code_marker_start, region.get_offset_end(), Marker.T.CODE)
  377. def get_marker_types(self):
  378. return Marker.T
  379. def are_markers_loaded(self):
  380. return self.markers != None
  381. def __repr__(self):
  382. return Data.__repr__(self) + " and regions " + self.regions.__repr__()
  383. class BasePlugin(object):
  384. def initialize(self):
  385. pass
  386. def terminate(self):
  387. pass
  388. def set_name(self, name):
  389. self.name = name
  390. def get_name(self):
  391. if hasattr(self, 'name') == False:
  392. return None
  393. return self.name
  394. def set_version(self, version):
  395. self.version = version
  396. def get_version(self):
  397. if hasattr(self, 'version') == False:
  398. return None
  399. return self.version
  400. def set_plugin_loader(self, loader):
  401. self.plugin_loader = loader
  402. def get_plugin_loader(self):
  403. if hasattr(self, 'plugin_loader') == False:
  404. return None
  405. return self.plugin_loader
  406. class Plugin(BasePlugin):
  407. class Field(object):
  408. def __init__(self, name, ftype, non_zero=False):
  409. self.name = name
  410. self.type = ftype
  411. self.non_zero = non_zero
  412. class Property(object):
  413. def __init__(self, name, value):
  414. self.name = name
  415. self.value = value
  416. def initialize(self, namespace=None, support_regions=True, fields=[], properties=[]):
  417. super(Plugin, self).initialize()
  418. if hasattr(self, 'is_updated') == False:
  419. self.is_updated = False # original initialization
  420. db_loader = self.get_plugin_loader().get_database_loader()
  421. if namespace == None:
  422. namespace = self.get_name()
  423. if (len(fields) != 0 or len(properties) != 0):
  424. prev_version = db_loader.set_property(self.get_name() + ":version", self.get_version())
  425. if prev_version != self.get_version():
  426. self.is_updated = True
  427. for prop in properties:
  428. assert(prop.name != 'version')
  429. prev_prop = db_loader.set_property(self.get_name() + ":" + prop.name, prop.value)
  430. if prev_prop != prop.value:
  431. self.is_updated = True
  432. if len(fields) != 0:
  433. namespace_obj = db_loader.create_namespace(namespace,
  434. support_regions=support_regions,
  435. version=self.get_version())
  436. for field in fields:
  437. is_created = namespace_obj.add_field(field.name, field.type, non_zero=field.non_zero)
  438. assert(is_created != None)
  439. # if field is created (not cloned from the previous db),
  440. # mark the plug-in as updated in order to trigger full rescan
  441. self.is_updated = self.is_updated or is_created
  442. class MetricPlugin(Plugin):
  443. def declare_metric(self, is_active, field,
  444. pattern_to_search_or_map_of_patterns,
  445. marker_type_mask=Marker.T.ANY,
  446. region_type_mask=FileRegionData.T.ANY,
  447. exclude_subregions=True,
  448. merge_markers=False):
  449. if hasattr(self, '_fields') == False:
  450. self._fields = {}
  451. if isinstance(pattern_to_search_or_map_of_patterns, dict):
  452. map_of_patterns = pattern_to_search_or_map_of_patterns
  453. else:
  454. map_of_patterns = {'default': pattern_to_search_or_map_of_patterns}
  455. if is_active == True:
  456. self._fields[field.name] = (field,
  457. marker_type_mask,
  458. exclude_subregions,
  459. merge_markers,
  460. map_of_patterns,
  461. region_type_mask)
  462. def is_active(self, metric_name = None):
  463. if metric_name == None:
  464. return (len(self._fields.keys()) > 0)
  465. return (metric_name in self._fields.keys())
  466. def get_fields(self):
  467. result = []
  468. for key in self._fields.keys():
  469. result.append(self._fields[key][0])
  470. return result
  471. def count_if_active(self, metric_name, data, namespace=None, alias='default'):
  472. if self.is_active(metric_name) == False:
  473. return
  474. if namespace == None:
  475. namespace = self.get_name()
  476. field_data = self._fields[metric_name]
  477. text = data.get_content()
  478. # TODO raise exception if alias is bad
  479. pattern_to_search = field_data[4][alias]
  480. for region in data.iterate_regions(filter_group=field_data[5]):
  481. count = 0
  482. for marker in data.iterate_markers(
  483. filter_group = field_data[1],
  484. region_id = region.get_id(),
  485. exclude_children = field_data[2],
  486. merge=field_data[3]):
  487. count += len(pattern_to_search.findall(text, marker.get_offset_begin(), marker.get_offset_end()))
  488. region.set_data(namespace, metric_name, count)
  489. class InterfaceNotImplemented(Exception):
  490. def __init__(self, obj):
  491. import sys
  492. Exception.__init__(self, "Method '"
  493. + sys._getframe(1).f_code.co_name
  494. + "' has not been implemented for "
  495. + str(obj.__class__))
  496. class IConfigurable(object):
  497. def configure(self, options):
  498. raise InterfaceNotImplemented(self)
  499. def declare_configuration(self, optparser):
  500. raise InterfaceNotImplemented(self)
  501. class IRunable(object):
  502. def run(self, args):
  503. raise InterfaceNotImplemented(self)
  504. class IParser(object):
  505. def process(self, parent, data, is_updated):
  506. raise InterfaceNotImplemented(self)
  507. class ICode(object):
  508. pass
  509. class ITool(object):
  510. def run(self, tool_args):
  511. raise InterfaceNotImplemented(self)
  512. class CallbackNotImplemented(Exception):
  513. def __init__(self, obj, callback_name):
  514. Exception.__init__(self, "Callback '"
  515. + callback_name
  516. + "' has not been implemented for "
  517. + str(obj.__class__))
  518. class Child(object):
  519. def notify(self, parent, callback_name, *args):
  520. if hasattr(self, callback_name) == False:
  521. raise CallbackNotImplemented(self, callback_name)
  522. self.__getattribute__(callback_name)(parent, *args)
  523. def subscribe_by_parents_name(self, parent_name, callback_name='callback'):
  524. self.get_plugin_loader().get_plugin(parent_name).subscribe(self, callback_name)
  525. def subscribe_by_parents_interface(self, interface, callback_name='callback'):
  526. for plugin in self.get_plugin_loader().iterate_plugins():
  527. if isinstance(plugin, interface):
  528. plugin.subscribe(self, callback_name)
  529. class Parent(object):
  530. def init_Parent(self):
  531. if hasattr(self, 'children') == False:
  532. self.children = []
  533. def subscribe(self, obj, callback_name):
  534. self.init_Parent()
  535. if (isinstance(obj, Child) == False):
  536. raise TypeError()
  537. self.children.append((obj,callback_name))
  538. def unsubscribe(self, obj, callback_name):
  539. self.init_Parent()
  540. self.children.remove((obj, callback_name))
  541. def notify_children(self, *args):
  542. self.init_Parent()
  543. for child in self.children:
  544. child[0].notify(self, child[1], *args)
  545. def iterate_children(self):
  546. self.init_Parent()
  547. for child in self.children:
  548. yield child
  549. # TODO re-factor and remove this
  550. class ExitError(Exception):
  551. def __init__(self, plugin, reason):
  552. if plugin != None:
  553. Exception.__init__(self, "Plugin '"
  554. + plugin.get_name()
  555. + "' requested abnormal termination: "
  556. + reason)
  557. else:
  558. Exception.__init__(self, "'Abnormal termination requested: "
  559. + reason)