Upstream version 11.40.277.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 contextlib
10 import os
11 import re
12 import shutil
13 import tempfile
14 import zipfile
15
16 from jinja2 import contextfilter
17
18 import mojom.generate.generator as generator
19 import mojom.generate.module as mojom
20 from mojom.generate.template_expander import UseJinja
21
22
23 GENERATOR_PREFIX = 'java'
24
25 _HEADER_SIZE = 8
26
27 _spec_to_java_type = {
28   mojom.BOOL.spec: 'boolean',
29   mojom.DCPIPE.spec: 'org.chromium.mojo.system.DataPipe.ConsumerHandle',
30   mojom.DOUBLE.spec: 'double',
31   mojom.DPPIPE.spec: 'org.chromium.mojo.system.DataPipe.ProducerHandle',
32   mojom.FLOAT.spec: 'float',
33   mojom.HANDLE.spec: 'org.chromium.mojo.system.UntypedHandle',
34   mojom.INT16.spec: 'short',
35   mojom.INT32.spec: 'int',
36   mojom.INT64.spec: 'long',
37   mojom.INT8.spec: 'byte',
38   mojom.MSGPIPE.spec: 'org.chromium.mojo.system.MessagePipeHandle',
39   mojom.NULLABLE_DCPIPE.spec:
40       'org.chromium.mojo.system.DataPipe.ConsumerHandle',
41   mojom.NULLABLE_DPPIPE.spec:
42       'org.chromium.mojo.system.DataPipe.ProducerHandle',
43   mojom.NULLABLE_HANDLE.spec: 'org.chromium.mojo.system.UntypedHandle',
44   mojom.NULLABLE_MSGPIPE.spec: 'org.chromium.mojo.system.MessagePipeHandle',
45   mojom.NULLABLE_SHAREDBUFFER.spec:
46       'org.chromium.mojo.system.SharedBufferHandle',
47   mojom.NULLABLE_STRING.spec: 'String',
48   mojom.SHAREDBUFFER.spec: 'org.chromium.mojo.system.SharedBufferHandle',
49   mojom.STRING.spec: 'String',
50   mojom.UINT16.spec: 'short',
51   mojom.UINT32.spec: 'int',
52   mojom.UINT64.spec: 'long',
53   mojom.UINT8.spec: 'byte',
54 }
55
56 _spec_to_decode_method = {
57   mojom.BOOL.spec:                  'readBoolean',
58   mojom.DCPIPE.spec:                'readConsumerHandle',
59   mojom.DOUBLE.spec:                'readDouble',
60   mojom.DPPIPE.spec:                'readProducerHandle',
61   mojom.FLOAT.spec:                 'readFloat',
62   mojom.HANDLE.spec:                'readUntypedHandle',
63   mojom.INT16.spec:                 'readShort',
64   mojom.INT32.spec:                 'readInt',
65   mojom.INT64.spec:                 'readLong',
66   mojom.INT8.spec:                  'readByte',
67   mojom.MSGPIPE.spec:               'readMessagePipeHandle',
68   mojom.NULLABLE_DCPIPE.spec:       'readConsumerHandle',
69   mojom.NULLABLE_DPPIPE.spec:       'readProducerHandle',
70   mojom.NULLABLE_HANDLE.spec:       'readUntypedHandle',
71   mojom.NULLABLE_MSGPIPE.spec:      'readMessagePipeHandle',
72   mojom.NULLABLE_SHAREDBUFFER.spec: 'readSharedBufferHandle',
73   mojom.NULLABLE_STRING.spec:       'readString',
74   mojom.SHAREDBUFFER.spec:          'readSharedBufferHandle',
75   mojom.STRING.spec:                'readString',
76   mojom.UINT16.spec:                'readShort',
77   mojom.UINT32.spec:                'readInt',
78   mojom.UINT64.spec:                'readLong',
79   mojom.UINT8.spec:                 'readByte',
80 }
81
82 _java_primitive_to_boxed_type = {
83   'boolean': 'Boolean',
84   'byte':    'Byte',
85   'double':  'Double',
86   'float':   'Float',
87   'int':     'Integer',
88   'long':    'Long',
89   'short':   'Short',
90 }
91
92
93 def NameToComponent(name):
94   # insert '_' between anything and a Title name (e.g, HTTPEntry2FooBar ->
95   # HTTP_Entry2_FooBar)
96   name = re.sub('([^_])([A-Z][^A-Z_]+)', r'\1_\2', name)
97   # insert '_' between non upper and start of upper blocks (e.g.,
98   # HTTP_Entry2_FooBar -> HTTP_Entry2_Foo_Bar)
99   name = re.sub('([^A-Z_])([A-Z])', r'\1_\2', name)
100   return [x.lower() for x in name.split('_')]
101
102 def UpperCamelCase(name):
103   return ''.join([x.capitalize() for x in NameToComponent(name)])
104
105 def CamelCase(name):
106   uccc = UpperCamelCase(name)
107   return uccc[0].lower() + uccc[1:]
108
109 def ConstantStyle(name):
110   components = NameToComponent(name)
111   if components[0] == 'k' and len(components) > 1:
112     components = components[1:]
113   # variable cannot starts with a digit.
114   if components[0][0].isdigit():
115     components[0] = '_' + components[0]
116   return '_'.join([x.upper() for x in components])
117
118 def GetNameForElement(element):
119   if (mojom.IsEnumKind(element) or mojom.IsInterfaceKind(element) or
120       mojom.IsStructKind(element)):
121     return UpperCamelCase(element.name)
122   if mojom.IsInterfaceRequestKind(element):
123     return GetNameForElement(element.kind)
124   if isinstance(element, (mojom.Method,
125                           mojom.Parameter,
126                           mojom.Field)):
127     return CamelCase(element.name)
128   if isinstance(element,  mojom.EnumValue):
129     return (GetNameForElement(element.enum) + '.' +
130             ConstantStyle(element.name))
131   if isinstance(element, (mojom.NamedValue,
132                           mojom.Constant,
133                           mojom.EnumField)):
134     return ConstantStyle(element.name)
135   raise Exception('Unexpected element: %s' % element)
136
137 def GetInterfaceResponseName(method):
138   return UpperCamelCase(method.name + 'Response')
139
140 def ParseStringAttribute(attribute):
141   assert isinstance(attribute, basestring)
142   return attribute
143
144 def GetJavaTrueFalse(value):
145   return 'true' if value else 'false'
146
147 def GetArrayNullabilityFlags(kind):
148     """Returns nullability flags for an array type, see Decoder.java.
149
150     As we have dedicated decoding functions for arrays, we have to pass
151     nullability information about both the array itself, as well as the array
152     element type there.
153     """
154     assert mojom.IsArrayKind(kind)
155     ARRAY_NULLABLE   = \
156         'org.chromium.mojo.bindings.BindingsHelper.ARRAY_NULLABLE'
157     ELEMENT_NULLABLE = \
158         'org.chromium.mojo.bindings.BindingsHelper.ELEMENT_NULLABLE'
159     NOTHING_NULLABLE = \
160         'org.chromium.mojo.bindings.BindingsHelper.NOTHING_NULLABLE'
161
162     flags_to_set = []
163     if mojom.IsNullableKind(kind):
164         flags_to_set.append(ARRAY_NULLABLE)
165     if mojom.IsNullableKind(kind.kind):
166         flags_to_set.append(ELEMENT_NULLABLE)
167
168     if not flags_to_set:
169         flags_to_set = [NOTHING_NULLABLE]
170     return ' | '.join(flags_to_set)
171
172
173 def AppendEncodeDecodeParams(initial_params, context, kind, bit):
174   """ Appends standard parameters shared between encode and decode calls. """
175   params = list(initial_params)
176   if (kind == mojom.BOOL):
177     params.append(str(bit))
178   if mojom.IsReferenceKind(kind):
179     if mojom.IsArrayKind(kind):
180       params.append(GetArrayNullabilityFlags(kind))
181     else:
182       params.append(GetJavaTrueFalse(mojom.IsNullableKind(kind)))
183   if mojom.IsArrayKind(kind):
184     params.append(GetArrayExpectedLength(kind))
185   if mojom.IsInterfaceKind(kind):
186     params.append('%s.MANAGER' % GetJavaType(context, kind))
187   if mojom.IsArrayKind(kind) and mojom.IsInterfaceKind(kind.kind):
188     params.append('%s.MANAGER' % GetJavaType(context, kind.kind))
189   return params
190
191
192 @contextfilter
193 def DecodeMethod(context, kind, offset, bit):
194   def _DecodeMethodName(kind):
195     if mojom.IsArrayKind(kind):
196       return _DecodeMethodName(kind.kind) + 's'
197     if mojom.IsEnumKind(kind):
198       return _DecodeMethodName(mojom.INT32)
199     if mojom.IsInterfaceRequestKind(kind):
200       return 'readInterfaceRequest'
201     if mojom.IsInterfaceKind(kind):
202       return 'readServiceInterface'
203     return _spec_to_decode_method[kind.spec]
204   methodName = _DecodeMethodName(kind)
205   params = AppendEncodeDecodeParams([ str(offset) ], context, kind, bit)
206   return '%s(%s)' % (methodName, ', '.join(params))
207
208 @contextfilter
209 def EncodeMethod(context, kind, variable, offset, bit):
210   params = AppendEncodeDecodeParams(
211       [ variable, str(offset) ], context, kind, bit)
212   return 'encode(%s)' % ', '.join(params)
213
214 def GetPackage(module):
215   if 'JavaPackage' in module.attributes:
216     return ParseStringAttribute(module.attributes['JavaPackage'])
217   # Default package.
218   if module.namespace:
219     return 'org.chromium.mojom.' + module.namespace
220   return 'org.chromium.mojom'
221
222 def GetNameForKind(context, kind):
223   def _GetNameHierachy(kind):
224     hierachy = []
225     if kind.parent_kind:
226       hierachy = _GetNameHierachy(kind.parent_kind)
227     hierachy.append(GetNameForElement(kind))
228     return hierachy
229
230   module = context.resolve('module')
231   elements = []
232   if GetPackage(module) != GetPackage(kind.module):
233     elements += [GetPackage(kind.module)]
234   elements += _GetNameHierachy(kind)
235   return '.'.join(elements)
236
237 def GetBoxedJavaType(context, kind):
238   unboxed_type = GetJavaType(context, kind, False)
239   if unboxed_type in _java_primitive_to_boxed_type:
240     return _java_primitive_to_boxed_type[unboxed_type]
241   return unboxed_type
242
243 @contextfilter
244 def GetJavaType(context, kind, boxed=False):
245   if boxed:
246     return GetBoxedJavaType(context, kind)
247   if mojom.IsStructKind(kind) or mojom.IsInterfaceKind(kind):
248     return GetNameForKind(context, kind)
249   if mojom.IsInterfaceRequestKind(kind):
250     return ('org.chromium.mojo.bindings.InterfaceRequest<%s>' %
251             GetNameForKind(context, kind.kind))
252   if mojom.IsMapKind(kind):
253     return 'java.util.Map<%s, %s>' % (
254         GetBoxedJavaType(context, kind.key_kind),
255         GetBoxedJavaType(context, kind.value_kind))
256   if mojom.IsArrayKind(kind):
257     return '%s[]' % GetJavaType(context, kind.kind)
258   if mojom.IsEnumKind(kind):
259     return 'int'
260   return _spec_to_java_type[kind.spec]
261
262 @contextfilter
263 def DefaultValue(context, field):
264   assert field.default
265   if isinstance(field.kind, mojom.Struct):
266     assert field.default == 'default'
267     return 'new %s()' % GetJavaType(context, field.kind)
268   return '(%s) %s' % (
269       GetJavaType(context, field.kind),
270       ExpressionToText(context, field.default, kind_spec=field.kind.spec))
271
272 @contextfilter
273 def ConstantValue(context, constant):
274   return '(%s) %s' % (
275       GetJavaType(context, constant.kind),
276       ExpressionToText(context, constant.value, kind_spec=constant.kind.spec))
277
278 @contextfilter
279 def NewArray(context, kind, size):
280   if mojom.IsArrayKind(kind.kind):
281     return NewArray(context, kind.kind, size) + '[]'
282   return 'new %s[%s]' % (GetJavaType(context, kind.kind), size)
283
284 @contextfilter
285 def ExpressionToText(context, token, kind_spec=''):
286   def _TranslateNamedValue(named_value):
287     entity_name = GetNameForElement(named_value)
288     if named_value.parent_kind:
289       return GetJavaType(context, named_value.parent_kind) + '.' + entity_name
290     # Handle the case where named_value is a module level constant:
291     if not isinstance(named_value, mojom.EnumValue):
292       entity_name = (GetConstantsMainEntityName(named_value.module) + '.' +
293                       entity_name)
294     if GetPackage(named_value.module) == GetPackage(context.resolve('module')):
295       return entity_name
296     return GetPackage(named_value.module) + '.' + entity_name
297
298   if isinstance(token, mojom.NamedValue):
299     return _TranslateNamedValue(token)
300   if kind_spec.startswith('i') or kind_spec.startswith('u'):
301     # Add Long suffix to all integer literals.
302     number = ast.literal_eval(token.lstrip('+ '))
303     if not isinstance(number, (int, long)):
304       raise ValueError('got unexpected type %r for int literal %r' % (
305           type(number), token))
306     # If the literal is too large to fit a signed long, convert it to the
307     # equivalent signed long.
308     if number >= 2 ** 63:
309       number -= 2 ** 64
310     return '%dL' % number
311   if isinstance(token, mojom.BuiltinValue):
312     if token.value == 'double.INFINITY':
313       return 'java.lang.Double.POSITIVE_INFINITY'
314     if token.value == 'double.NEGATIVE_INFINITY':
315       return 'java.lang.Double.NEGATIVE_INFINITY'
316     if token.value == 'double.NAN':
317       return 'java.lang.Double.NaN'
318     if token.value == 'float.INFINITY':
319       return 'java.lang.Float.POSITIVE_INFINITY'
320     if token.value == 'float.NEGATIVE_INFINITY':
321       return 'java.lang.Float.NEGATIVE_INFINITY'
322     if token.value == 'float.NAN':
323       return 'java.lang.Float.NaN'
324   return token
325
326 def GetArrayKind(kind, size = None):
327   if size is None:
328     return mojom.Array(kind)
329   else:
330     array = mojom.Array(kind, 0)
331     array.java_map_size = size
332     return array
333
334 def GetArrayExpectedLength(kind):
335   if mojom.IsArrayKind(kind) and kind.length is not None:
336     return getattr(kind, 'java_map_size', str(kind.length))
337   else:
338     return 'org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH'
339
340 def IsPointerArrayKind(kind):
341   if not mojom.IsArrayKind(kind):
342     return False
343   sub_kind = kind.kind
344   return mojom.IsObjectKind(sub_kind)
345
346 def GetResponseStructFromMethod(method):
347   return generator.GetDataHeader(
348       False, generator.GetResponseStructFromMethod(method))
349
350 def GetStructFromMethod(method):
351   return generator.GetDataHeader(
352       False, generator.GetStructFromMethod(method))
353
354 def GetConstantsMainEntityName(module):
355   if 'JavaConstantsClassName' in module.attributes:
356     return ParseStringAttribute(module.attributes['JavaConstantsClassName'])
357   # This constructs the name of the embedding classes for module level constants
358   # by extracting the mojom's filename and prepending it to Constants.
359   return (UpperCamelCase(module.path.split('/')[-1].rsplit('.', 1)[0]) +
360           'Constants')
361
362 def GetMethodOrdinalName(method):
363   return ConstantStyle(method.name) + '_ORDINAL'
364
365 def HasMethodWithResponse(interface):
366   for method in interface.methods:
367     if method.response_parameters is not None:
368       return True
369   return False
370
371 def HasMethodWithoutResponse(interface):
372   for method in interface.methods:
373     if method.response_parameters is None:
374       return True
375   return False
376
377 @contextlib.contextmanager
378 def TempDir():
379   dirname = tempfile.mkdtemp()
380   try:
381     yield dirname
382   finally:
383     shutil.rmtree(dirname)
384
385 def ZipContentInto(root, zip_filename):
386   with zipfile.ZipFile(zip_filename, 'w') as zip_file:
387     for dirname, _, files in os.walk(root):
388       for filename in files:
389         path = os.path.join(dirname, filename)
390         path_in_archive = os.path.relpath(path, root)
391         zip_file.write(path, path_in_archive)
392
393 class Generator(generator.Generator):
394
395   java_filters = {
396     'array_expected_length': GetArrayExpectedLength,
397     'array': GetArrayKind,
398     'constant_value': ConstantValue,
399     'decode_method': DecodeMethod,
400     'default_value': DefaultValue,
401     'encode_method': EncodeMethod,
402     'expression_to_text': ExpressionToText,
403     'has_method_without_response': HasMethodWithoutResponse,
404     'has_method_with_response': HasMethodWithResponse,
405     'interface_response_name': GetInterfaceResponseName,
406     'is_array_kind': mojom.IsArrayKind,
407     'is_handle': mojom.IsNonInterfaceHandleKind,
408     'is_map_kind': mojom.IsMapKind,
409     'is_nullable_kind': mojom.IsNullableKind,
410     'is_pointer_array_kind': IsPointerArrayKind,
411     'is_reference_kind': mojom.IsReferenceKind,
412     'is_struct_kind': mojom.IsStructKind,
413     'java_true_false': GetJavaTrueFalse,
414     'java_type': GetJavaType,
415     'method_ordinal_name': GetMethodOrdinalName,
416     'name': GetNameForElement,
417     'new_array': NewArray,
418     'response_struct_from_method': GetResponseStructFromMethod,
419     'struct_from_method': GetStructFromMethod,
420     'struct_size': lambda ps: ps.GetTotalSize() + _HEADER_SIZE,
421   }
422
423   def GetJinjaExports(self):
424     return {
425       'package': GetPackage(self.module),
426     }
427
428   def GetJinjaExportsForInterface(self, interface):
429     exports = self.GetJinjaExports()
430     exports.update({'interface': interface})
431     if interface.client:
432       for client in self.module.interfaces:
433         if client.name == interface.client:
434           exports.update({'client': client})
435     return exports
436
437   @UseJinja('java_templates/enum.java.tmpl', filters=java_filters)
438   def GenerateEnumSource(self, enum):
439     exports = self.GetJinjaExports()
440     exports.update({'enum': enum})
441     return exports
442
443   @UseJinja('java_templates/struct.java.tmpl', filters=java_filters)
444   def GenerateStructSource(self, struct):
445     exports = self.GetJinjaExports()
446     exports.update({'struct': struct})
447     return exports
448
449   @UseJinja('java_templates/interface.java.tmpl', filters=java_filters)
450   def GenerateInterfaceSource(self, interface):
451     return self.GetJinjaExportsForInterface(interface)
452
453   @UseJinja('java_templates/interface_internal.java.tmpl', filters=java_filters)
454   def GenerateInterfaceInternalSource(self, interface):
455     return self.GetJinjaExportsForInterface(interface)
456
457   @UseJinja('java_templates/constants.java.tmpl', filters=java_filters)
458   def GenerateConstantsSource(self, module):
459     exports = self.GetJinjaExports()
460     exports.update({'main_entity': GetConstantsMainEntityName(module),
461                     'constants': module.constants})
462     return exports
463
464   def DoGenerateFiles(self):
465     if not os.path.exists(self.output_dir):
466       try:
467         os.makedirs(self.output_dir)
468       except:
469         # Ignore errors on directory creation.
470         pass
471
472     # Keep this above the others as .GetStructs() changes the state of the
473     # module, annotating structs with required information.
474     for struct in self.GetStructs():
475       self.Write(self.GenerateStructSource(struct),
476                  '%s.java' % GetNameForElement(struct))
477
478     for enum in self.module.enums:
479       self.Write(self.GenerateEnumSource(enum),
480                  '%s.java' % GetNameForElement(enum))
481
482     for interface in self.module.interfaces:
483       self.Write(self.GenerateInterfaceSource(interface),
484                  '%s.java' % GetNameForElement(interface))
485       self.Write(self.GenerateInterfaceInternalSource(interface),
486                  '%s_Internal.java' % GetNameForElement(interface))
487
488     if self.module.constants:
489       self.Write(self.GenerateConstantsSource(self.module),
490                  '%s.java' % GetConstantsMainEntityName(self.module))
491
492   def GenerateFiles(self, unparsed_args):
493     parser = argparse.ArgumentParser()
494     parser.add_argument('--java_output_directory', dest='java_output_directory')
495     args = parser.parse_args(unparsed_args)
496     package_path = GetPackage(self.module).replace('.', '/')
497
498     # Generate the java files in a temporary directory and place a single
499     # srcjar in the output directory.
500     basename = self.MatchMojomFilePath("%s.srcjar" % self.module.name)
501     zip_filename = os.path.join(self.output_dir, basename)
502     with TempDir() as temp_java_root:
503       self.output_dir = os.path.join(temp_java_root, package_path)
504       self.DoGenerateFiles();
505       ZipContentInto(temp_java_root, zip_filename)
506
507     if args.java_output_directory:
508       # If requested, generate the java files directly into indicated directory.
509       self.output_dir = os.path.join(args.java_output_directory, package_path)
510       self.DoGenerateFiles();
511
512   def GetJinjaParameters(self):
513     return {
514       'lstrip_blocks': True,
515       'trim_blocks': True,
516     }
517
518   def GetGlobals(self):
519     return {
520       'namespace': self.module.namespace,
521       'module': self.module,
522     }