Upstream version 10.39.225.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':
112     components = components[1:]
113   return '_'.join([x.upper() for x in components])
114
115 def GetNameForElement(element):
116   if (mojom.IsEnumKind(element) or mojom.IsInterfaceKind(element) or
117       mojom.IsStructKind(element)):
118     return UpperCamelCase(element.name)
119   if mojom.IsInterfaceRequestKind(element):
120     return GetNameForElement(element.kind)
121   if isinstance(element, (mojom.Method,
122                           mojom.Parameter,
123                           mojom.Field)):
124     return CamelCase(element.name)
125   if isinstance(element,  mojom.EnumValue):
126     return (GetNameForElement(element.enum) + '.' +
127             ConstantStyle(element.name))
128   if isinstance(element, (mojom.NamedValue,
129                           mojom.Constant)):
130     return ConstantStyle(element.name)
131   raise Exception('Unexpected element: ' % element)
132
133 def GetInterfaceResponseName(method):
134   return UpperCamelCase(method.name + 'Response')
135
136 def ParseStringAttribute(attribute):
137   assert isinstance(attribute, basestring)
138   return attribute
139
140 def GetJavaTrueFalse(value):
141   return 'true' if value else 'false'
142
143 def GetArrayNullabilityFlags(kind):
144     """Returns nullability flags for an array type, see Decoder.java.
145
146     As we have dedicated decoding functions for arrays, we have to pass
147     nullability information about both the array itself, as well as the array
148     element type there.
149     """
150     assert mojom.IsAnyArrayKind(kind)
151     ARRAY_NULLABLE   = \
152         'org.chromium.mojo.bindings.BindingsHelper.ARRAY_NULLABLE'
153     ELEMENT_NULLABLE = \
154         'org.chromium.mojo.bindings.BindingsHelper.ELEMENT_NULLABLE'
155     NOTHING_NULLABLE = \
156         'org.chromium.mojo.bindings.BindingsHelper.NOTHING_NULLABLE'
157
158     flags_to_set = []
159     if mojom.IsNullableKind(kind):
160         flags_to_set.append(ARRAY_NULLABLE)
161     if mojom.IsNullableKind(kind.kind):
162         flags_to_set.append(ELEMENT_NULLABLE)
163
164     if not flags_to_set:
165         flags_to_set = [NOTHING_NULLABLE]
166     return ' | '.join(flags_to_set)
167
168
169 def AppendEncodeDecodeParams(initial_params, context, kind, bit):
170   """ Appends standard parameters shared between encode and decode calls. """
171   params = list(initial_params)
172   if (kind == mojom.BOOL):
173     params.append(str(bit))
174   if mojom.IsReferenceKind(kind):
175     if mojom.IsAnyArrayKind(kind):
176       params.append(GetArrayNullabilityFlags(kind))
177     else:
178       params.append(GetJavaTrueFalse(mojom.IsNullableKind(kind)))
179   if mojom.IsAnyArrayKind(kind):
180     if mojom.IsFixedArrayKind(kind):
181       params.append(str(kind.length))
182     else:
183       params.append(
184         'org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH');
185   if mojom.IsInterfaceKind(kind):
186     params.append('%s.MANAGER' % GetJavaType(context, kind))
187   if mojom.IsAnyArrayKind(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.IsAnyArrayKind(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   return 'org.chromium.mojom.' + module.namespace
219
220 def GetNameForKind(context, kind):
221   def _GetNameHierachy(kind):
222     hierachy = []
223     if kind.parent_kind:
224       hierachy = _GetNameHierachy(kind.parent_kind)
225     hierachy.append(GetNameForElement(kind))
226     return hierachy
227
228   module = context.resolve('module')
229   elements = []
230   if GetPackage(module) != GetPackage(kind.module):
231     elements += [GetPackage(kind.module)]
232   elements += _GetNameHierachy(kind)
233   return '.'.join(elements)
234
235 def GetBoxedJavaType(context, kind):
236   unboxed_type = GetJavaType(context, kind, False)
237   if unboxed_type in _java_primitive_to_boxed_type:
238     return _java_primitive_to_boxed_type[unboxed_type]
239   return unboxed_type
240
241 @contextfilter
242 def GetJavaType(context, kind, boxed=False):
243   if boxed:
244     return GetBoxedJavaType(context, kind)
245   if mojom.IsStructKind(kind) or mojom.IsInterfaceKind(kind):
246     return GetNameForKind(context, kind)
247   if mojom.IsInterfaceRequestKind(kind):
248     return ('org.chromium.mojo.bindings.InterfaceRequest<%s>' %
249             GetNameForKind(context, kind.kind))
250   if mojom.IsAnyArrayKind(kind):
251     return '%s[]' % GetJavaType(context, kind.kind)
252   if mojom.IsEnumKind(kind):
253     return 'int'
254   return _spec_to_java_type[kind.spec]
255
256 @contextfilter
257 def DefaultValue(context, field):
258   assert field.default
259   if isinstance(field.kind, mojom.Struct):
260     assert field.default == 'default'
261     return 'new %s()' % GetJavaType(context, field.kind)
262   return '(%s) %s' % (
263       GetJavaType(context, field.kind),
264       ExpressionToText(context, field.default, kind_spec=field.kind.spec))
265
266 @contextfilter
267 def ConstantValue(context, constant):
268   return '(%s) %s' % (
269       GetJavaType(context, constant.kind),
270       ExpressionToText(context, constant.value, kind_spec=constant.kind.spec))
271
272 @contextfilter
273 def NewArray(context, kind, size):
274   if mojom.IsAnyArrayKind(kind.kind):
275     return NewArray(context, kind.kind, size) + '[]'
276   return 'new %s[%s]' % (GetJavaType(context, kind.kind), size)
277
278 @contextfilter
279 def ExpressionToText(context, token, kind_spec=''):
280   def _TranslateNamedValue(named_value):
281     entity_name = GetNameForElement(named_value)
282     if named_value.parent_kind:
283       return GetJavaType(context, named_value.parent_kind) + '.' + entity_name
284     # Handle the case where named_value is a module level constant:
285     if not isinstance(named_value, mojom.EnumValue):
286       entity_name = (GetConstantsMainEntityName(named_value.module) + '.' +
287                       entity_name)
288     if GetPackage(named_value.module) == GetPackage(context.resolve('module')):
289       return entity_name
290     return GetPackage(named_value.module) + '.' + entity_name
291
292   if isinstance(token, mojom.NamedValue):
293     return _TranslateNamedValue(token)
294   if kind_spec.startswith('i') or kind_spec.startswith('u'):
295     # Add Long suffix to all integer literals.
296     number = ast.literal_eval(token.lstrip('+ '))
297     if not isinstance(number, (int, long)):
298       raise ValueError('got unexpected type %r for int literal %r' % (
299           type(number), token))
300     # If the literal is too large to fit a signed long, convert it to the
301     # equivalent signed long.
302     if number >= 2 ** 63:
303       number -= 2 ** 64
304     return '%dL' % number
305   if isinstance(token, mojom.BuiltinValue):
306     if token.value == 'double.INFINITY':
307       return 'java.lang.Double.POSITIVE_INFINITY'
308     if token.value == 'double.NEGATIVE_INFINITY':
309       return 'java.lang.Double.NEGATIVE_INFINITY'
310     if token.value == 'double.NAN':
311       return 'java.lang.Double.NaN'
312     if token.value == 'float.INFINITY':
313       return 'java.lang.Float.POSITIVE_INFINITY'
314     if token.value == 'float.NEGATIVE_INFINITY':
315       return 'java.lang.Float.NEGATIVE_INFINITY'
316     if token.value == 'float.NAN':
317       return 'java.lang.Float.NaN'
318   return token
319
320 def IsPointerArrayKind(kind):
321   if not mojom.IsAnyArrayKind(kind):
322     return False
323   sub_kind = kind.kind
324   return mojom.IsObjectKind(sub_kind)
325
326 def GetResponseStructFromMethod(method):
327   return generator.GetDataHeader(
328       False, generator.GetResponseStructFromMethod(method))
329
330 def GetStructFromMethod(method):
331   return generator.GetDataHeader(
332       False, generator.GetStructFromMethod(method))
333
334 def GetConstantsMainEntityName(module):
335   if 'JavaConstantsClassName' in module.attributes:
336     return ParseStringAttribute(module.attributes['JavaConstantsClassName'])
337   # This constructs the name of the embedding classes for module level constants
338   # by extracting the mojom's filename and prepending it to Constants.
339   return (UpperCamelCase(module.path.split('/')[-1].rsplit('.', 1)[0]) +
340           'Constants')
341
342 def GetMethodOrdinalName(method):
343   return ConstantStyle(method.name) + '_ORDINAL'
344
345 def HasMethodWithResponse(interface):
346   for method in interface.methods:
347     if method.response_parameters:
348       return True
349   return False
350
351 def HasMethodWithoutResponse(interface):
352   for method in interface.methods:
353     if not method.response_parameters:
354       return True
355   return False
356
357 @contextlib.contextmanager
358 def TempDir():
359   dirname = tempfile.mkdtemp()
360   try:
361     yield dirname
362   finally:
363     shutil.rmtree(dirname)
364
365 def ZipContentInto(root, zip_filename):
366   with zipfile.ZipFile(zip_filename, 'w') as zip_file:
367     for dirname, _, files in os.walk(root):
368       for filename in files:
369         path = os.path.join(dirname, filename)
370         path_in_archive = os.path.relpath(path, root)
371         zip_file.write(path, path_in_archive)
372
373 class Generator(generator.Generator):
374
375   java_filters = {
376     'interface_response_name': GetInterfaceResponseName,
377     'constant_value': ConstantValue,
378     'default_value': DefaultValue,
379     'decode_method': DecodeMethod,
380     'expression_to_text': ExpressionToText,
381     'encode_method': EncodeMethod,
382     'has_method_with_response': HasMethodWithResponse,
383     'has_method_without_response': HasMethodWithoutResponse,
384     'is_fixed_array_kind': mojom.IsFixedArrayKind,
385     'is_handle': mojom.IsNonInterfaceHandleKind,
386     'is_nullable_kind': mojom.IsNullableKind,
387     'is_pointer_array_kind': IsPointerArrayKind,
388     'is_struct_kind': mojom.IsStructKind,
389     'java_type': GetJavaType,
390     'java_true_false': GetJavaTrueFalse,
391     'method_ordinal_name': GetMethodOrdinalName,
392     'name': GetNameForElement,
393     'new_array': NewArray,
394     'response_struct_from_method': GetResponseStructFromMethod,
395     'struct_from_method': GetStructFromMethod,
396     'struct_size': lambda ps: ps.GetTotalSize() + _HEADER_SIZE,
397   }
398
399   def GetJinjaExports(self):
400     return {
401       'package': GetPackage(self.module),
402     }
403
404   def GetJinjaExportsForInterface(self, interface):
405     exports = self.GetJinjaExports()
406     exports.update({'interface': interface})
407     if interface.client:
408       for client in self.module.interfaces:
409         if client.name == interface.client:
410           exports.update({'client': client})
411     return exports
412
413   @UseJinja('java_templates/enum.java.tmpl', filters=java_filters)
414   def GenerateEnumSource(self, enum):
415     exports = self.GetJinjaExports()
416     exports.update({'enum': enum})
417     return exports
418
419   @UseJinja('java_templates/struct.java.tmpl', filters=java_filters)
420   def GenerateStructSource(self, struct):
421     exports = self.GetJinjaExports()
422     exports.update({'struct': struct})
423     return exports
424
425   @UseJinja('java_templates/interface.java.tmpl', filters=java_filters)
426   def GenerateInterfaceSource(self, interface):
427     return self.GetJinjaExportsForInterface(interface)
428
429   @UseJinja('java_templates/interface_internal.java.tmpl', filters=java_filters)
430   def GenerateInterfaceInternalSource(self, interface):
431     return self.GetJinjaExportsForInterface(interface)
432
433   @UseJinja('java_templates/constants.java.tmpl', filters=java_filters)
434   def GenerateConstantsSource(self, module):
435     exports = self.GetJinjaExports()
436     exports.update({'main_entity': GetConstantsMainEntityName(module),
437                     'constants': module.constants})
438     return exports
439
440   def DoGenerateFiles(self):
441     if not os.path.exists(self.output_dir):
442       try:
443         os.makedirs(self.output_dir)
444       except:
445         # Ignore errors on directory creation.
446         pass
447
448     # Keep this above the others as .GetStructs() changes the state of the
449     # module, annotating structs with required information.
450     for struct in self.GetStructs():
451       self.Write(self.GenerateStructSource(struct),
452                  '%s.java' % GetNameForElement(struct))
453
454     for enum in self.module.enums:
455       self.Write(self.GenerateEnumSource(enum),
456                  '%s.java' % GetNameForElement(enum))
457
458     for interface in self.module.interfaces:
459       self.Write(self.GenerateInterfaceSource(interface),
460                  '%s.java' % GetNameForElement(interface))
461       self.Write(self.GenerateInterfaceInternalSource(interface),
462                  '%s_Internal.java' % GetNameForElement(interface))
463
464     if self.module.constants:
465       self.Write(self.GenerateConstantsSource(self.module),
466                  '%s.java' % GetConstantsMainEntityName(self.module))
467
468   def GenerateFiles(self, unparsed_args):
469     parser = argparse.ArgumentParser()
470     parser.add_argument('--java_output_directory', dest='java_output_directory')
471     args = parser.parse_args(unparsed_args)
472     package_path = GetPackage(self.module).replace('.', '/')
473
474     # Generate the java files in a temporary directory and place a single
475     # srcjar in the output directory.
476     zip_filename = os.path.join(self.output_dir,
477                                 "%s.srcjar" % self.module.name)
478     with TempDir() as temp_java_root:
479       self.output_dir = os.path.join(temp_java_root, package_path)
480       self.DoGenerateFiles();
481       ZipContentInto(temp_java_root, zip_filename)
482
483     if args.java_output_directory:
484       # If requested, generate the java files directly into indicated directory.
485       self.output_dir = os.path.join(args.java_output_directory, package_path)
486       self.DoGenerateFiles();
487
488   def GetJinjaParameters(self):
489     return {
490       'lstrip_blocks': True,
491       'trim_blocks': True,
492     }
493
494   def GetGlobals(self):
495     return {
496       'namespace': self.module.namespace,
497       'module': self.module,
498     }