java.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. #
  2. # Metrix++, Copyright 2009-2019, Metrix++ Project
  3. # Link: https://github.com/metrixplusplus/metrixplusplus
  4. #
  5. # This file is a part of Metrix++ Tool.
  6. #
  7. import re
  8. import binascii
  9. import mpp.api
  10. import mpp.cout
  11. class Plugin(mpp.api.Plugin, mpp.api.Parent, mpp.api.IParser, mpp.api.IConfigurable, mpp.api.ICode):
  12. def declare_configuration(self, parser):
  13. parser.add_option("--std.code.java.files", default="*.java",
  14. help="Enumerates filename extensions to match Java files [default: %default]")
  15. def configure(self, options):
  16. self.files = options.__dict__['std.code.java.files'].split(',')
  17. self.files.sort() # sorted list goes to properties
  18. def initialize(self):
  19. mpp.api.Plugin.initialize(self, properties=[
  20. self.Property('files', ','.join(self.files))
  21. ])
  22. self.get_plugin('std.tools.collect').register_parser(self.files, self)
  23. def process(self, parent, data, is_updated):
  24. is_updated = is_updated or self.is_updated
  25. count_mismatched_brackets = 0
  26. if is_updated == True:
  27. count_mismatched_brackets = JavaCodeParser().run(data)
  28. self.notify_children(data, is_updated)
  29. return count_mismatched_brackets
  30. class JavaCodeParser(object):
  31. regex_cpp = re.compile(r'''
  32. //(?=\n|\r\n|\r) # Match Java style comments (empty comment line)
  33. | //.*?(?=\n|\r\n|\r) # Match Java style comments
  34. # NOTE: end of line is NOT consumed
  35. # NOTE: it is slightly different in C++
  36. | /\*\*/ # Match C style comments (empty comment line)
  37. # NOTE: it is slightly different in C++
  38. | /\*.*?\*/ # Match C style comments
  39. # NOTE: it is slightly different in C++
  40. | \'(?:\\.|[^\\\'])*\' # Match quoted strings
  41. | "(?:\\.|[^\\"])*" # Match double quoted strings
  42. | (?P<fn_name>([@]?[_$a-zA-Z][_$a-zA-Z0-9]*))\s*[(] # Match function
  43. # NOTE: Matches attributes which are excluded later
  44. # NOTE: Java may include $ in the name
  45. # LIMITATION: if there are comments after function name
  46. # and before '(', it is not detected
  47. | ((?P<block_type>\bclass|\binterface) # Match class or namespace
  48. (?P<block_name>(\s+[_$a-zA-Z][_$a-zA-Z0-9]*)))
  49. # NOTE: noname instances are impossible in Java
  50. # LIMITATION: if there are comments between keyword and name,
  51. # it is not detected
  52. | [{};] # Match block start/end and statement separator
  53. # NOTE: C++ parser includes processing of <> and :
  54. # to handle template definitions, it is easier in Java
  55. | ((?:\n|\r\n|\r)\s*(?:\n|\r\n|\r)) # Match double empty line
  56. ''',
  57. re.DOTALL | re.MULTILINE | re.VERBOSE
  58. )
  59. # \r\n goes before \r in order to consume right number of lines on Unix for Windows files
  60. regex_ln = re.compile(r'(\n)|(\r\n)|(\r)')
  61. def run(self, data):
  62. self.__init__() # Go to initial state if it is called twice
  63. return self.parse(data)
  64. def finalize_block(self, text, block, block_end):
  65. if block['type'] != '__global__':
  66. # do not trim spaces for __global__region
  67. space_match = re.match('^\s*', text[block['start']:block_end], re.MULTILINE)
  68. block['start'] += space_match.end() # trim spaces at the beginning
  69. block['end'] = block_end
  70. start_pos = block['start']
  71. crc32 = 0
  72. for child in block['children']:
  73. # exclude children
  74. crc32 = binascii.crc32(text[start_pos:child['start']], crc32)
  75. start_pos = child['end']
  76. block['checksum'] = binascii.crc32(text[start_pos:block['end']], crc32) & 0xffffffff # to match python 3
  77. def add_lines_data(self, text, blocks):
  78. def add_lines_data_rec(self, text, blocks):
  79. for each in blocks:
  80. # add line begin
  81. self.total_current += len(self.regex_ln.findall(text, self.total_last_pos, each['start']))
  82. each['line_begin'] = self.total_current
  83. self.total_last_pos = each['start']
  84. # process enclosed
  85. add_lines_data_rec(self, text, each['children'])
  86. # add line end
  87. self.total_current += len(self.regex_ln.findall(text, self.total_last_pos, each['end']))
  88. each['line_end'] = self.total_current
  89. self.total_last_pos = each['end']
  90. self.total_last_pos = 0
  91. self.total_current = 1
  92. add_lines_data_rec(self, text, blocks)
  93. def add_regions(self, data, blocks):
  94. # Note: data.add_region() internals depend on special ordering of regions
  95. # in order to identify enclosed regions efficiently
  96. def add_regions_rec(self, data, blocks):
  97. def get_type_id(data, named_type):
  98. if named_type == "function":
  99. return mpp.api.Region.T.FUNCTION
  100. elif named_type == "class":
  101. return mpp.api.Region.T.CLASS
  102. elif named_type == "interface":
  103. return mpp.api.Region.T.INTERFACE
  104. elif named_type == "__global__":
  105. return mpp.api.Region.T.GLOBAL
  106. else:
  107. assert(False)
  108. for each in blocks:
  109. data.add_region(each['name'], each['start'], each['end'],
  110. each['line_begin'], each['line_end'], each['cursor'],
  111. get_type_id(data, each['type']), each['checksum'])
  112. add_regions_rec(self, data, each['children'])
  113. add_regions_rec(self, data, blocks)
  114. def parse(self, data):
  115. def reset_next_block(start):
  116. return {'name':'', 'start':start, 'cursor':0, 'type':''}
  117. count_mismatched_brackets = 0
  118. text = data.get_content()
  119. indent_current = 0;
  120. blocks = [{'name':'__global__', 'start':0, 'cursor':0, 'type':'__global__', 'indent_start':indent_current, 'children':[]}]
  121. curblk = 0
  122. next_block = reset_next_block(0)
  123. cursor_last_pos = 0
  124. cursor_current = 1
  125. for m in re.finditer(self.regex_cpp, text):
  126. # Comment
  127. if text[m.start()] == '/':
  128. data.add_marker(m.start(), m.end(), mpp.api.Marker.T.COMMENT)
  129. # String
  130. elif text[m.start()] == '"' or text[m.start()] == '\'':
  131. data.add_marker(m.start() + 1, m.end() - 1, mpp.api.Marker.T.STRING)
  132. # Statement end
  133. elif text[m.start()] == ';':
  134. # Reset next block name and start
  135. next_block['name'] = ""
  136. next_block['start'] = m.end() # potential region start
  137. # Double end line
  138. elif text[m.start()] == '\n' or text[m.start()] == '\r':
  139. # Reset next block start, if has not been named yet
  140. if next_block['name'] == "":
  141. next_block['start'] = m.end() # potential region start
  142. # Block start...
  143. elif text[m.start()] == '{':
  144. # shift indent right
  145. indent_current += 1
  146. # ... if name detected previously
  147. if next_block['name'] != '': # - Start of enclosed block
  148. blocks.append({'name':next_block['name'],
  149. 'start':next_block['start'],
  150. 'cursor':next_block['cursor'],
  151. 'type':next_block['type'],
  152. 'indent_start':indent_current,
  153. 'children':[]})
  154. next_block = reset_next_block(m.end())
  155. curblk += 1
  156. # ... reset next block start, otherwise
  157. else: # - unknown type of block start
  158. next_block['start'] = m.end() # potential region start
  159. # Block end...
  160. elif text[m.start()] == '}':
  161. # ... if indent level matches the start
  162. if blocks[curblk]['indent_start'] == indent_current:
  163. next_block = reset_next_block(m.end())
  164. if curblk == 0:
  165. mpp.cout.notify(data.get_path(),
  166. cursor_current + len(self.regex_ln.findall(text, cursor_last_pos, m.start())),
  167. mpp.cout.SEVERITY_WARNING,
  168. "Non-matching closing bracket '}' detected.")
  169. count_mismatched_brackets += 1
  170. continue
  171. self.finalize_block(text, blocks[curblk], m.end())
  172. assert(blocks[curblk]['type'] != '__global__')
  173. curblk -= 1
  174. assert(curblk >= 0)
  175. blocks[curblk]['children'].append(blocks.pop())
  176. # shift indent left
  177. indent_current -= 1
  178. if indent_current < 0:
  179. mpp.cout.notify(data.get_path(),
  180. cursor_current + len(self.regex_ln.findall(text, cursor_last_pos, m.start())),
  181. mpp.cout.SEVERITY_WARNING,
  182. "Non-matching closing bracket '}' detected.")
  183. count_mismatched_brackets += 1
  184. indent_current = 0
  185. # Potential class, interface
  186. elif m.group('block_type') != None:
  187. if next_block['name'] == "":
  188. # - 'name'
  189. next_block['name'] = m.group('block_name').strip()
  190. # - 'cursor'
  191. cursor_current += len(self.regex_ln.findall(text, cursor_last_pos, m.start('block_name')))
  192. cursor_last_pos = m.start('block_name')
  193. next_block['cursor'] = cursor_current
  194. # - 'type'
  195. next_block['type'] = m.group('block_type').strip()
  196. # - 'start' detected earlier
  197. # Potential function name detected...
  198. elif m.group('fn_name') != None:
  199. # ... if outside of a function
  200. # (do not detect functions enclosed directly in a function, i.e. without classes)
  201. # ... and other name before has not been matched
  202. if blocks[curblk]['type'] != 'function' and (next_block['name'] == "") and m.group('fn_name')[0] != '@':
  203. # - 'name'
  204. next_block['name'] = m.group('fn_name').strip()
  205. # - 'cursor'
  206. cursor_current += len(self.regex_ln.findall(text, cursor_last_pos, m.start('fn_name')))
  207. cursor_last_pos = m.start('fn_name')
  208. # NOTE: cursor could be collected together with line_begin, line_end,
  209. # but we keep it here separately for easier debugging of file parsing problems
  210. next_block['cursor'] = cursor_current
  211. # - 'type'
  212. next_block['type'] = 'function'
  213. # - 'start' detected earlier
  214. else:
  215. assert(len("Unknown match by regular expression") == 0)
  216. while indent_current > 0:
  217. # log all
  218. mpp.cout.notify(data.get_path(),
  219. cursor_current + len(self.regex_ln.findall(text, cursor_last_pos, len(text))),
  220. mpp.cout.SEVERITY_WARNING,
  221. "Non-matching opening bracket '{' detected.")
  222. count_mismatched_brackets += 1
  223. indent_current -= 1
  224. for (ind, each) in enumerate(blocks):
  225. each = each # used
  226. block = blocks[len(blocks) - 1 - ind]
  227. self.finalize_block(text, block, len(text))
  228. self.add_lines_data(text, blocks)
  229. self.add_regions(data, blocks)
  230. return count_mismatched_brackets