Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / mojo / public / tools / bindings / generators / mojom_java_generator.py
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Generates java source files from a mojom.Module."""
6
7 import argparse
8 import ast
9 import os
10 import re
11
12 from jinja2 import contextfilter
13
14 import mojom.generate.generator as generator
15 import mojom.generate.module as mojom
16 from mojom.generate.template_expander import UseJinja
17
18
19 GENERATOR_PREFIX = 'java'
20
21 _HEADER_SIZE = 8
22
23 _spec_to_java_type = {
24   mojom.BOOL.spec: 'boolean',
25   mojom.DCPIPE.spec: 'org.chromium.mojo.system.DataPipe.ConsumerHandle',
26   mojom.DOUBLE.spec: 'double',
27   mojom.DPPIPE.spec: 'org.chromium.mojo.system.DataPipe.ProducerHandle',
28   mojom.FLOAT.spec: 'float',
29   mojom.HANDLE.spec: 'org.chromium.mojo.system.UntypedHandle',
30   mojom.INT16.spec: 'short',
31   mojom.INT32.spec: 'int',
32   mojom.INT64.spec: 'long',
33   mojom.INT8.spec: 'byte',
34   mojom.MSGPIPE.spec: 'org.chromium.mojo.system.MessagePipeHandle',
35   mojom.NULLABLE_DCPIPE.spec:
36       'org.chromium.mojo.system.DataPipe.ConsumerHandle',
37   mojom.NULLABLE_DPPIPE.spec:
38       'org.chromium.mojo.system.DataPipe.ProducerHandle',
39   mojom.NULLABLE_HANDLE.spec: 'org.chromium.mojo.system.UntypedHandle',
40   mojom.NULLABLE_MSGPIPE.spec: 'org.chromium.mojo.system.MessagePipeHandle',
41   mojom.NULLABLE_SHAREDBUFFER.spec:
42       'org.chromium.mojo.system.SharedBufferHandle',
43   mojom.NULLABLE_STRING.spec: 'String',
44   mojom.SHAREDBUFFER.spec: 'org.chromium.mojo.system.SharedBufferHandle',
45   mojom.STRING.spec: 'String',
46   mojom.UINT16.spec: 'short',
47   mojom.UINT32.spec: 'int',
48   mojom.UINT64.spec: 'long',
49   mojom.UINT8.spec: 'byte',
50 }
51
52 _spec_to_decode_method = {
53   mojom.BOOL.spec:                  'readBoolean',
54   mojom.DCPIPE.spec:                'readConsumerHandle',
55   mojom.DOUBLE.spec:                'readDouble',
56   mojom.DPPIPE.spec:                'readProducerHandle',
57   mojom.FLOAT.spec:                 'readFloat',
58   mojom.HANDLE.spec:                'readUntypedHandle',
59   mojom.INT16.spec:                 'readShort',
60   mojom.INT32.spec:                 'readInt',
61   mojom.INT64.spec:                 'readLong',
62   mojom.INT8.spec:                  'readByte',
63   mojom.MSGPIPE.spec:               'readMessagePipeHandle',
64   mojom.NULLABLE_DCPIPE.spec:       'readConsumerHandle',
65   mojom.NULLABLE_DPPIPE.spec:       'readProducerHandle',
66   mojom.NULLABLE_HANDLE.spec:       'readUntypedHandle',
67   mojom.NULLABLE_MSGPIPE.spec:      'readMessagePipeHandle',
68   mojom.NULLABLE_SHAREDBUFFER.spec: 'readSharedBufferHandle',
69   mojom.NULLABLE_STRING.spec:       'readString',
70   mojom.SHAREDBUFFER.spec:          'readSharedBufferHandle',
71   mojom.STRING.spec:                'readString',
72   mojom.UINT16.spec:                'readShort',
73   mojom.UINT32.spec:                'readInt',
74   mojom.UINT64.spec:                'readLong',
75   mojom.UINT8.spec:                 'readByte',
76 }
77
78 _java_primitive_to_boxed_type = {
79   'boolean': 'Boolean',
80   'byte':    'Byte',
81   'double':  'Double',
82   'float':   'Float',
83   'int':     'Integer',
84   'long':    'Long',
85   'short':   'Short',
86 }
87
88
89 def NameToComponent(name):
90   # insert '_' between anything and a Title name (e.g, HTTPEntry2FooBar ->
91   # HTTP_Entry2_FooBar)
92   name = re.sub('([^_])([A-Z][^A-Z_]+)', r'\1_\2', name)
93   # insert '_' between non upper and start of upper blocks (e.g.,
94   # HTTP_Entry2_FooBar -> HTTP_Entry2_Foo_Bar)
95   name = re.sub('([^A-Z_])([A-Z])', r'\1_\2', name)
96   return [x.lower() for x in name.split('_')]
97
98 def CapitalizeFirst(string):
99   return string[0].upper() + string[1:]
100
101 def UpperCamelCase(name):
102   return ''.join([CapitalizeFirst(x) for x in NameToComponent(name)])
103
104 def CamelCase(name):
105   uccc = UpperCamelCase(name)
106   return uccc[0].lower() + uccc[1:]
107
108 def ConstantStyle(name):
109   components = NameToComponent(name)
110   if components[0] == 'k':
111     components = components[1:]
112   return '_'.join([x.upper() for x in components])
113
114 def GetNameForElement(element):
115   if (mojom.IsEnumKind(element) or mojom.IsInterfaceKind(element) or
116       mojom.IsStructKind(element)):
117     return UpperCamelCase(element.name)
118   if mojom.IsInterfaceRequestKind(element):
119     return GetNameForElement(element.kind)
120   if isinstance(element, (mojom.Method,
121                           mojom.Parameter,
122                           mojom.Field)):
123     return CamelCase(element.name)
124   if isinstance(element,  mojom.EnumValue):
125     return (UpperCamelCase(element.enum_name) + '.' +
126             ConstantStyle(element.name))
127   if isinstance(element, (mojom.NamedValue,
128                           mojom.Constant)):
129     return ConstantStyle(element.name)
130   raise Exception("Unexpected element: " % element)
131
132 def GetInterfaceResponseName(method):
133   return UpperCamelCase(method.name + 'Response')
134
135 def ParseStringAttribute(attribute):
136   assert isinstance(attribute, basestring)
137   return attribute
138
139 @contextfilter
140 def DecodeMethod(context, kind, offset, bit):
141   def _DecodeMethodName(kind):
142     if mojom.IsAnyArrayKind(kind):
143       return _DecodeMethodName(kind.kind) + 's'
144     if mojom.IsEnumKind(kind):
145       return _DecodeMethodName(mojom.INT32)
146     if mojom.IsInterfaceRequestKind(kind):
147       return "readInterfaceRequest"
148     if mojom.IsInterfaceKind(kind):
149       return "readServiceInterface"
150     return _spec_to_decode_method[kind.spec]
151   methodName = _DecodeMethodName(kind)
152   additionalParams = ''
153   if (kind == mojom.BOOL):
154     additionalParams = ', %d' % bit
155   if mojom.IsInterfaceKind(kind):
156     additionalParams = ', %s.BUILDER' % GetJavaType(context, kind)
157   if mojom.IsAnyArrayKind(kind) and mojom.IsInterfaceKind(kind.kind):
158     additionalParams = ', %s.BUILDER' % GetJavaType(context, kind.kind)
159   return '%s(%s%s)' % (methodName, offset, additionalParams)
160
161 @contextfilter
162 def EncodeMethod(context, kind, variable, offset, bit):
163   additionalParams = ''
164   if (kind == mojom.BOOL):
165     additionalParams = ', %d' % bit
166   if mojom.IsInterfaceKind(kind):
167     additionalParams = ', %s.BUILDER' % GetJavaType(context, kind)
168   if mojom.IsAnyArrayKind(kind) and mojom.IsInterfaceKind(kind.kind):
169     additionalParams = ', %s.BUILDER' % GetJavaType(context, kind.kind)
170   return 'encode(%s, %s%s)' % (variable, offset, additionalParams)
171
172 def GetPackage(module):
173   if 'JavaPackage' in module.attributes:
174     return ParseStringAttribute(module.attributes['JavaPackage'])
175   # Default package.
176   return "org.chromium.mojom." + module.namespace
177
178 def GetNameForKind(context, kind):
179   def _GetNameHierachy(kind):
180     hierachy = []
181     if kind.parent_kind:
182       hierachy = _GetNameHierachy(kind.parent_kind)
183     hierachy.append(GetNameForElement(kind))
184     return hierachy
185
186   module = context.resolve('module')
187   elements = []
188   if GetPackage(module) != GetPackage(kind.module):
189     elements += [GetPackage(kind.module)]
190   elements += _GetNameHierachy(kind)
191   return '.'.join(elements)
192
193 def GetBoxedJavaType(context, kind):
194   unboxed_type = GetJavaType(context, kind, False)
195   if unboxed_type in _java_primitive_to_boxed_type:
196     return _java_primitive_to_boxed_type[unboxed_type]
197   return unboxed_type
198
199 @contextfilter
200 def GetJavaType(context, kind, boxed=False):
201   if boxed:
202     return GetBoxedJavaType(context, kind)
203   if mojom.IsStructKind(kind) or mojom.IsInterfaceKind(kind):
204     return GetNameForKind(context, kind)
205   if mojom.IsInterfaceRequestKind(kind):
206     return ("org.chromium.mojo.bindings.InterfaceRequest<%s>" %
207             GetNameForKind(context, kind.kind))
208   if mojom.IsAnyArrayKind(kind):
209     return "%s[]" % GetJavaType(context, kind.kind)
210   if mojom.IsEnumKind(kind):
211     return "int"
212   return _spec_to_java_type[kind.spec]
213
214 @contextfilter
215 def DefaultValue(context, field):
216   assert field.default
217   if isinstance(field.kind, mojom.Struct):
218     assert field.default == "default"
219     return "new %s()" % GetJavaType(context, field.kind)
220   return "(%s) %s" % (
221       GetJavaType(context, field.kind),
222       ExpressionToText(context, field.default, kind_spec=field.kind.spec))
223
224 @contextfilter
225 def ConstantValue(context, constant):
226   return "(%s) %s" % (
227       GetJavaType(context, constant.kind),
228       ExpressionToText(context, constant.value, kind_spec=constant.kind.spec))
229
230 @contextfilter
231 def NewArray(context, kind, size):
232   if mojom.IsAnyArrayKind(kind.kind):
233     return NewArray(context, kind.kind, size) + '[]'
234   return 'new %s[%s]' % (GetJavaType(context, kind.kind), size)
235
236 @contextfilter
237 def ExpressionToText(context, token, kind_spec=''):
238   def _TranslateNamedValue(named_value):
239     entity_name = GetNameForElement(named_value)
240     if named_value.parent_kind:
241       return GetJavaType(context, named_value.parent_kind) + '.' + entity_name
242     # Handle the case where named_value is a module level constant:
243     if not isinstance(named_value, mojom.EnumValue):
244       entity_name = (GetConstantsMainEntityName(named_value.module) + '.' +
245                       entity_name)
246     if GetPackage(named_value.module) == GetPackage(context.resolve('module')):
247       return entity_name
248     return GetPackage(named_value.module) + '.' + entity_name
249
250   if isinstance(token, mojom.NamedValue):
251     return _TranslateNamedValue(token)
252   if kind_spec.startswith('i') or kind_spec.startswith('u'):
253     # Add Long suffix to all integer literals.
254     number = ast.literal_eval(token.lstrip('+ '))
255     if not isinstance(number, (int, long)):
256       raise ValueError('got unexpected type %r for int literal %r' % (
257           type(number), token))
258     # If the literal is too large to fit a signed long, convert it to the
259     # equivalent signed long.
260     if number >= 2 ** 63:
261       number -= 2 ** 64
262     return '%dL' % number
263   return token
264
265 def IsPointerArrayKind(kind):
266   if not mojom.IsAnyArrayKind(kind):
267     return False
268   sub_kind = kind.kind
269   return mojom.IsObjectKind(sub_kind)
270
271 def GetConstantsMainEntityName(module):
272   if 'JavaConstantsClassName' in module.attributes:
273     return ParseStringAttribute(module.attributes['JavaConstantsClassName'])
274   # This constructs the name of the embedding classes for module level constants
275   # by extracting the mojom's filename and prepending it to Constants.
276   return (UpperCamelCase(module.path.split('/')[-1].rsplit('.', 1)[0]) +
277           'Constants')
278
279 class Generator(generator.Generator):
280
281   java_filters = {
282     "interface_response_name": GetInterfaceResponseName,
283     "constant_value": ConstantValue,
284     "default_value": DefaultValue,
285     "decode_method": DecodeMethod,
286     "expression_to_text": ExpressionToText,
287     "encode_method": EncodeMethod,
288     "is_handle": mojom.IsNonInterfaceHandleKind,
289     "is_pointer_array_kind": IsPointerArrayKind,
290     "is_struct_kind": mojom.IsStructKind,
291     "java_type": GetJavaType,
292     "name": GetNameForElement,
293     "new_array": NewArray,
294     "struct_size": lambda ps: ps.GetTotalSize() + _HEADER_SIZE,
295   }
296
297   def GetJinjaExports(self):
298     return {
299       "module": self.module,
300       "package": GetPackage(self.module),
301     }
302
303   @UseJinja("java_templates/enum.java.tmpl", filters=java_filters)
304   def GenerateEnumSource(self, enum):
305     exports = self.GetJinjaExports()
306     exports.update({"enum": enum})
307     return exports
308
309   @UseJinja("java_templates/struct.java.tmpl", filters=java_filters)
310   def GenerateStructSource(self, struct):
311     exports = self.GetJinjaExports()
312     exports.update({"struct": struct})
313     return exports
314
315   @UseJinja("java_templates/interface.java.tmpl", filters=java_filters)
316   def GenerateInterfaceSource(self, interface):
317     exports = self.GetJinjaExports()
318     exports.update({"interface": interface})
319     if interface.client:
320       for client in self.module.interfaces:
321         if client.name == interface.client:
322           exports.update({"client": client})
323     return exports
324
325   @UseJinja("java_templates/constants.java.tmpl", filters=java_filters)
326   def GenerateConstantsSource(self, module):
327     exports = self.GetJinjaExports()
328     exports.update({"main_entity": GetConstantsMainEntityName(module),
329                     "constants": module.constants})
330     return exports
331
332   def GenerateFiles(self, unparsed_args):
333     parser = argparse.ArgumentParser()
334     parser.add_argument("--java_output_directory", dest="java_output_directory")
335     args = parser.parse_args(unparsed_args)
336     if self.output_dir and args.java_output_directory:
337       self.output_dir = os.path.join(args.java_output_directory,
338                                      GetPackage(self.module).replace('.', '/'))
339       if not os.path.exists(self.output_dir):
340         try:
341           os.makedirs(self.output_dir)
342         except:
343           # Ignore errors on directory creation.
344           pass
345
346     for enum in self.module.enums:
347       self.Write(self.GenerateEnumSource(enum),
348                  "%s.java" % GetNameForElement(enum))
349
350     for struct in self.module.structs:
351       self.Write(self.GenerateStructSource(struct),
352                  "%s.java" % GetNameForElement(struct))
353
354     for interface in self.module.interfaces:
355       self.Write(self.GenerateInterfaceSource(interface),
356                  "%s.java" % GetNameForElement(interface))
357
358     if self.module.constants:
359       self.Write(self.GenerateConstantsSource(self.module),
360                  "%s.java" % GetConstantsMainEntityName(self.module))
361
362   def GetJinjaParameters(self):
363     return {
364       'lstrip_blocks': True,
365       'trim_blocks': True,
366     }
367
368   def GetGlobals(self):
369     return {
370       'module': self.module,
371     }