a702ec6fec79469148ea7099bedcab64e4f00bf0
[platform/framework/web/crosswalk.git] / src / xwalk / tools / reflection_generator / java_class.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2014 Intel Corporation. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6
7 import os
8 import re
9
10 from string import Template
11
12 from java_class_component import Enum, Field
13 from java_method import Method
14
15 class JavaClassLoader(object):
16   """Manager class maintains all loaded java classes."""
17   def __init__(self, src_path, class_list):
18     self._src_path = src_path
19     self._java_data_map = {}
20     self._class_list = class_list
21
22   def IsInternalClass(self, clazz):
23     return clazz in self._class_list
24
25   def LoadJavaClass(self, clazz):
26     java_data = self._java_data_map.get(clazz, None)
27     if java_data:
28       return java_data
29
30     file_name = os.path.join(self._src_path, '%s.java' % clazz)
31     self._java_data_map[clazz] = self.LoadJavaFile(file_name)
32
33     return self._java_data_map[clazz]
34
35   def LoadJavaFile(self, file_name):
36     try:
37       file_handle = open(file_name, 'r')
38       file_content = file_handle.read()
39       file_handle.close()
40     except Exception:
41       print 'Error reading input Java file, please check.'
42       return
43
44     java_data = InternalJavaFileData(self)
45     java_data.SetClassContent(file_content)
46
47     return java_data
48
49   def MangleInternalNameToBridgeName(self, internal_name):
50     if not self.IsInternalClass(internal_name):
51       return internal_name
52     else:
53       return internal_name.replace('Internal', 'Bridge')
54
55   def MangleInternalNameToWrapperName(self, internal_name):
56     if not self.IsInternalClass(internal_name):
57       return internal_name
58     else:
59       return internal_name.replace('Internal', '')
60
61   def GenerateDoc(self, doc):
62     if not doc:
63       return ''
64     def ReplaceInternal(matchobj):
65       match = matchobj.group(0)
66       if self.IsInternalClass(match):
67         return self.LoadJavaClass(match).wrapper_name
68       else:
69         return match
70     return re.sub('XWalk[a-zA-Z_0-9]*Internal', ReplaceInternal, doc)
71
72 class InternalJavaFileData(object):
73   """Data class stores the generator information of internal class."""
74   ANNOTATION_CREATE_INTERNALLY = 'createInternally'
75   ANNOTATION_CREATE_EXTERNALLY = 'createExternally'
76   ANNOTATION_EXTEND_CLASS = 'extendClass'
77   ANNOTATION_NO_INSTANCE = 'noInstance'
78   ANNOTATION_INSTANCE = 'instance'
79   ANNOTATION_IMPL = 'impl'
80
81   def __init__(self, class_loader):
82     self._class_loader = class_loader
83     self._class_name = ''
84     self._bridge_name = ''
85     self._wrapper_name = ''
86     self._class_type = ''  # class or interface
87     self._class_doc = ''
88     self._class_annotations = {}
89     self._methods = []
90     self._fields = []
91     self._imports = []
92     self._enums = {}
93     self._package_name = ''
94
95   def LoadJavaClass(self, clazz):
96     return self._class_loader.LoadJavaClass(clazz)
97
98   @property
99   def class_name(self):
100     return self._class_name
101
102   @property
103   def bridge_name(self):
104     return self._bridge_name
105
106   @property
107   def wrapper_name(self):
108     return self._wrapper_name
109
110   @property
111   def class_type(self):
112     return self._class_type
113
114   @property
115   def class_doc(self):
116     return self._class_doc
117
118   @property
119   def class_annotations(self):
120     return self._class_annotations
121
122   @property
123   def methods(self):
124     return self._methods
125
126   @property
127   def fields(self):
128     return self._fields
129
130   @property
131   def imports(self):
132     return self._imports
133
134   @property
135   def enums(self):
136     return self._enums
137
138   @property
139   def package_name(self):
140     return self._package_name
141
142   def SetClassContent(self, content):
143     self.ExtractPackageName(content)
144     self.ExtractImports(content)
145     self.ExtractClassProperties(content)
146     self.ExtractMethods(content)
147     self.ExtractFields(content)
148     self.ExtractEnums(content)
149
150   def ExtractPackageName(self, java_content):
151     package_re = re.compile('\s*package\s+(?P<package>[a-zA-Z0-9._]+)\s*;')
152     for match in re.finditer(package_re, java_content):
153       self._package_name = match.group('package')
154
155   def ExtractImports(self, java_content):
156     imports_re = re.compile('\s*import\s+(?P<imported>[a-zA-Z0-9._*]+)\s*;')
157     for match in re.finditer(imports_re, java_content):
158       imported = match.group('imported')
159       # Determine whether the import rule should be ignored for generated code.
160       # TODO: Currently we only use a blacklist to filter the import rule.
161       if imported.startswith('org.xwalk.core.internal') or \
162           imported.startswith('org.chromium'):
163         continue
164       self._imports.append(imported)
165
166   def ExtractClassProperties(self, java_content):
167     class_re = re.compile(
168         '(?P<class_doc>(\n\s*/\*\*.*\n(\s+\*(.)*\n)+\s+\*/\s*)?)\n'
169         '\s*@XWalkAPI\(?'
170         '(?P<annotation_content>[a-zA-Z0-9.,=\s]*)\)?'
171         '\s*public\s+([a-z]+\s+)*'
172         '(?P<type>(class|interface))\s+'
173         '(?P<class_name>[a-zA-Z0-9]*)')
174     for match in re.finditer(class_re, java_content):
175       annotation_content = match.group('annotation_content')
176       self._class_name = match.group('class_name')
177       self._bridge_name = \
178           self._class_loader.MangleInternalNameToBridgeName(self._class_name)
179       self._wrapper_name = \
180           self._class_loader.MangleInternalNameToWrapperName(self._class_name)
181       self._class_type = match.group('type')
182       self._class_doc = match.group('class_doc')
183       self.ParseClassAnnotations(annotation_content)
184
185   def ParseClassAnnotations(self, annotation):
186     """Class annotation contains the following optional attributes:
187         'extendClass' - The class have to extend
188         'createExternally' - boolean
189         'craeteInternally' - boolean
190         'noInstance' - boolean
191         'isConst' - boolean
192         'impl' - Class to impl
193         'instance - instance'"""
194     extend_class_re = re.compile('extendClass\s*=\s*'
195         '(?P<extend_class>[a-zA-Z0-9.]+)')
196     for match in re.finditer(extend_class_re, annotation):
197       extend_class = match.group('extend_class')
198       self._class_annotations['extendClass'] = extend_class
199
200     create_internally_re = re.compile('createInternally\s*=\s*'
201         '(?P<create_internally>(true|false))')
202     for match in re.finditer(create_internally_re, annotation):
203       create_internally = match.group('create_internally')
204       if create_internally == 'true':
205         self._class_annotations['createInternally'] = True
206       elif create_internally == 'false':
207         self._class_annotations['createInternally'] = False
208
209     create_externally_re = re.compile('createExternally\s*=\s*'
210         '(?P<create_externally>(true|false))')
211     for match in re.finditer(create_externally_re, annotation):
212       create_externally = match.group('create_externally')
213       if create_externally == 'true':
214         self._class_annotations['createExternally'] = True
215       elif create_externally == 'false':
216         self._class_annotations['createExternally'] = False
217
218     no_instance_re = re.compile('noInstance\s*=\s*'
219         '(?P<no_instance>(true|false))')
220     for match in re.finditer(no_instance_re, annotation):
221       no_instance = match.group('no_instance')
222       if no_instance == 'true':
223         self._class_annotations['noInstance'] = True
224       elif no_instance == 'false':
225         self._class_annotations['noInstance'] = False
226
227     is_const_re = re.compile('isConst\s*=\s*'
228         '(?P<is_const>(true|false))')
229     for match in re.finditer(is_const_re, annotation):
230       is_const = match.group('is_const')
231       if is_const == 'true':
232         self._class_annotations['isConst'] = True
233       elif is_const == 'false':
234         self._class_annotations['isConst'] = False
235
236     impl_re = re.compile('impl\s*=\s*'
237         '(?P<impl>[a-zA-Z0-9.]+)')
238     for match in re.finditer(impl_re, annotation):
239       impl = match.group('impl')
240       self._class_annotations['impl'] = impl
241
242     instance_re = re.compile('instance\s*=\s*'
243         '(?P<instance>[a-zA-Z0-9.]+)')
244     for match in re.finditer(instance_re, annotation):
245       instance = match.group('instance')
246       self._class_annotations['instance'] = instance
247
248   def ExtractMethods(self, java_content):
249     constructor_re = re.compile(
250         '(?P<method_doc>(\n\s*/\*\*.*\n(\s+\*(.)*\n)+\s+\*/\s*)?)\n'
251         '\s*@XWalkAPI\(?'
252         '(?P<method_annotation>[a-zA-Z0-9\$%,\s\(\)\{\};._"=]*)\)?'
253         '\s*public\s(?P<method_name>[a-zA-Z0-9]+)\('
254         '(?P<method_params>[a-zA-Z0-9\s,\[\]\>\<]*)\)')
255     for match in re.finditer(constructor_re, java_content):
256       method_annotation = match.group('method_annotation')
257       method_name = match.group('method_name')
258       method_params = match.group('method_params')
259       method_doc = match.group('method_doc')
260       method = Method(
261           self._class_name,
262           self._class_loader,
263           True, # is_constructor
264           False, # is_static
265           False, # is_abstract
266           method_name, None,
267           method_params, method_annotation, method_doc)
268       self._methods.append(method)
269
270     method_re = re.compile(
271         '(?P<method_doc>(\n\s*/\*\*.*\n(\s+\*(.)*\n)+\s+\*/\s*)?)\n'
272         '\s*@XWalkAPI\(?'
273         '(?P<method_annotation>[a-zA-Z0-9%,\s\(\)\{\};._"=]*)\)?'
274         '\s*public\s+(?P<method_return>[a-zA-Z0-9]+)\s+'
275         '(?P<method_name>[a-zA-Z0-9]+)\('
276         '(?P<method_params>[a-zA-Z0-9\s,\]\[\<\>]*)\)')
277     for match in re.finditer(method_re, java_content):
278       method_annotation = match.group('method_annotation')
279       method_name = match.group('method_name')
280       method_params = match.group('method_params')
281       method_return = match.group('method_return')
282       method_doc = match.group('method_doc')
283       method = Method(
284           self._class_name,
285           self._class_loader,
286           False, # is_constructor
287           False, # is_static
288           False, # is_abstract
289           method_name, method_return, method_params,
290           method_annotation, method_doc)
291       self._methods.append(method)
292
293     method_re = re.compile(
294         '(?P<method_doc>(\n\s*/\*\*.*\n(\s+\*(.)*\n)+\s+\*/\s*)?)\n'
295         '\s*@XWalkAPI\(?'
296         '(?P<method_annotation>[a-zA-Z0-9%,\s\(\)\{\};._"=]*)\)?'
297         '\s*public\s+static\s+(synchronized\s+)*'
298         '(?P<method_return>[a-zA-Z0-9]+)\s+'
299         '(?P<method_name>[a-zA-Z0-9]+)\('
300         '(?P<method_params>[a-zA-Z0-9\s,\[\]\<\>]*)\)')
301     for match in re.finditer(method_re, java_content):
302       method_annotation = match.group('method_annotation')
303       method_name = match.group('method_name')
304       method_params = match.group('method_params')
305       method_return = match.group('method_return')
306       method_doc = match.group('method_doc')
307       method = Method(
308           self._class_name,
309           self._class_loader,
310           False, # is_constructor
311           True, # is_static
312           False, # is_abstract
313           method_name, method_return, method_params,
314           method_annotation, method_doc)
315       self._methods.append(method)
316
317     method_re = re.compile(
318         '(?P<method_doc>(\n\s*/\*\*.*\n(\s+\*(.)*\n)+\s+\*/\s*)?)\n'
319         '\s*@XWalkAPI\(?'
320         '(?P<method_annotation>[a-zA-Z0-9%,\s\(\)\{\};._"=]*)\)?'
321         '\s*public\s+abstract\s+(synchronized\s+)*'
322         '(?P<method_return>[a-zA-Z0-9]+)\s+'
323         '(?P<method_name>[a-zA-Z0-9]+)\('
324         '(?P<method_params>[a-zA-Z0-9\s,\[\]\<\>]*)\)')
325     for match in re.finditer(method_re, java_content):
326       method_annotation = match.group('method_annotation')
327       method_name = match.group('method_name')
328       method_params = match.group('method_params')
329       method_return = match.group('method_return')
330       method_doc = match.group('method_doc')
331       method = Method(
332           self._class_name,
333           self._class_loader,
334           False, # is_constructor
335           False, # is_static
336           True, # is_abstract
337           method_name, method_return, method_params,
338           method_annotation, method_doc)
339       self._methods.append(method)
340
341   def ExtractFields(self, java_content):
342     field_re = re.compile(
343         '(?P<field_doc>(\n\s*/\*\*.*\n(\s+\*(.)*\n)+\s+\*/\s*)?)\n'
344         '\s*@XWalkAPI\s*public\s+static\s+final\s+'
345         '(?P<field_type>[a-zA-Z0-9_]+)\s+'
346         '(?P<field_name>[a-zA-Z0-9_]+)\s*=\s*'
347         '(?P<field_value>[a-zA-Z0-9-_"]+)\s*;')
348     for match in re.finditer(field_re, java_content):
349       field_type = match.group('field_type')
350       field_name = match.group('field_name')
351       field_value = match.group('field_value')
352       field_doc = match.group('field_doc')
353       field_object = Field(field_type, field_name, field_value, field_doc)
354       self._fields.append(field_object)
355
356   def ExtractEnums(self, java_content):
357     enum_re = re.compile(
358         '(?P<enum_doc>(\n\s*/\*\*.*\n(\s+\*(.)*\n)+\s+\*/\s*)?)\n'
359         '\s*@XWalkAPI\s*public\s+enum\s+'
360         '(?P<enum_name>[a-zA-Z0-9_]+)\s+{'
361         '(?P<enum_content>(.|\n)*?)\s*}')
362     for match in re.finditer(enum_re, java_content):
363       enum_name = match.group('enum_name')
364       enum_content = match.group('enum_content')
365       enum_doc = match.group('enum_doc')
366       enum_object = Enum(enum_name, enum_content, enum_doc)
367       self._enums[enum_name] = enum_object
368
369   def HasCreateInternallyAnnotation(self):
370     return self._class_annotations.get(
371         InternalJavaFileData.ANNOTATION_CREATE_INTERNALLY, False)
372
373   def UseAsInstanceInBridgeCall(self, var):
374     return '%s.getWrapper()' % self.UseAsReturnInBridgeSuperCall(var)
375
376   def UseAsInstanceInBridgeOverrideCall(self, var):
377     clazz = self._class_annotations.get('instance', self._class_name)
378     clazz = clazz.replace('.class', '')
379     return '(%s) %s' % (self.LoadJavaClass(clazz).bridge_name, var)
380
381   def UseAsReturnInBridgeSuperCall(self, var):
382     clazz = self._class_annotations.get('instance', self._class_name)
383     clazz = clazz.replace('.class', '')
384
385     if self.LoadJavaClass(clazz).class_annotations.get(
386         'createInternally', False):
387       typed_var_template = Template('(${VAR} instanceof ${BRIDGE_TYPE} ?'\
388           ' ((${BRIDGE_TYPE}) ${VAR} ) : new ${BRIDGE_TYPE}(${INTERNAL_VAR}))')
389       value = {'VAR': var,
390                'BRIDGE_TYPE': self.LoadJavaClass(clazz).bridge_name,
391                'INTERNAL_VAR': var if clazz == self._class_name else\
392                                    '(%s) %s' % (clazz, var)}
393       var = typed_var_template.substitute(value)
394
395     return var
396
397   def UseAsTypeInBridgeAndBridgeSuperCall(self):
398     clazz = self._class_annotations.get('instance', self._class_name)
399     clazz = clazz.replace('.class', '')
400
401     return self.LoadJavaClass(clazz).bridge_name
402
403   def UseAsInstanceInBridgeSuperCall(self, var):
404     # pylint: disable=R0201
405     return var
406
407   def UseAsInstanceInWrapperCall(self, var):
408     clazz = self._class_annotations.get('instance', self._class_name)
409     clazz = clazz.replace('.class', '')
410
411     if clazz != self._class_name:
412       var = '((%s) %s)' % (self.LoadJavaClass(clazz).wrapper_name, var)
413     return '%s.getBridge()' % var
414
415   def UseAsTypeInWrapperCall(self):
416     return self._wrapper_name
417
418   def GetFullBridgeName(self, subclass=None):
419     if not self._class_loader.IsInternalClass(self._class_name):
420       return self._class_name
421     else:
422       clazz = self._class_annotations.get(
423           InternalJavaFileData.ANNOTATION_INSTANCE, self._class_name)
424       clazz = clazz.replace('.class', '')
425       package_string = 'org.xwalk.core.internal.%s'
426       if not subclass:
427         return package_string % self.LoadJavaClass(clazz).bridge_name
428       else:
429         return package_string % (clazz + '$' + subclass)
430
431   def GetFullWrapperName(self, subclass=None):
432     if not self._class_loader.IsInternalClass(self._class_name):
433       return self._class_name
434     else:
435       if not subclass:
436         return "org.xwalk.core.%s" % self._wrapper_name
437       else:
438         return "org.xwalk.core.%s$%s" % (self._wrapper_name,
439                                          subclass.replace('Internal', ''))