Explicitly add python-numpy dependency to install-build-deps.
[chromium-blink-merge.git] / mojo / public / tools / bindings / generators / mojom_java_generator.py
blob4bede9a928170f0c00dc56fecb36214553ab1284
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.
5 """Generates java source files from a mojom.Module."""
7 import argparse
8 import ast
9 import contextlib
10 import os
11 import re
12 import shutil
13 import tempfile
14 import zipfile
16 from jinja2 import contextfilter
18 import mojom.generate.generator as generator
19 import mojom.generate.module as mojom
20 from mojom.generate.template_expander import UseJinja
23 GENERATOR_PREFIX = 'java'
25 _HEADER_SIZE = 8
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',
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',
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',
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('_')]
102 def UpperCamelCase(name):
103 return ''.join([x.capitalize() for x in NameToComponent(name)])
105 def CamelCase(name):
106 uccc = UpperCamelCase(name)
107 return uccc[0].lower() + uccc[1:]
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])
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)
137 def GetInterfaceResponseName(method):
138 return UpperCamelCase(method.name + 'Response')
140 def ParseStringAttribute(attribute):
141 assert isinstance(attribute, basestring)
142 return attribute
144 def GetJavaTrueFalse(value):
145 return 'true' if value else 'false'
147 def GetArrayNullabilityFlags(kind):
148 """Returns nullability flags for an array type, see Decoder.java.
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.
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'
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)
168 if not flags_to_set:
169 flags_to_set = [NOTHING_NULLABLE]
170 return ' | '.join(flags_to_set)
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
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))
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)
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'
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
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)
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
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]
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))
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))
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)
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
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
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
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'
340 def IsPointerArrayKind(kind):
341 if not mojom.IsArrayKind(kind):
342 return False
343 sub_kind = kind.kind
344 return mojom.IsObjectKind(sub_kind)
346 def GetResponseStructFromMethod(method):
347 return generator.GetDataHeader(
348 False, generator.GetResponseStructFromMethod(method))
350 def GetStructFromMethod(method):
351 return generator.GetDataHeader(
352 False, generator.GetStructFromMethod(method))
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')
362 def GetMethodOrdinalName(method):
363 return ConstantStyle(method.name) + '_ORDINAL'
365 def HasMethodWithResponse(interface):
366 for method in interface.methods:
367 if method.response_parameters is not None:
368 return True
369 return False
371 def HasMethodWithoutResponse(interface):
372 for method in interface.methods:
373 if method.response_parameters is None:
374 return True
375 return False
377 @contextlib.contextmanager
378 def TempDir():
379 dirname = tempfile.mkdtemp()
380 try:
381 yield dirname
382 finally:
383 shutil.rmtree(dirname)
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)
393 class Generator(generator.Generator):
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,
423 def GetJinjaExports(self):
424 return {
425 'package': GetPackage(self.module),
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
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
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
449 @UseJinja('java_templates/interface.java.tmpl', filters=java_filters)
450 def GenerateInterfaceSource(self, interface):
451 return self.GetJinjaExportsForInterface(interface)
453 @UseJinja('java_templates/interface_internal.java.tmpl', filters=java_filters)
454 def GenerateInterfaceInternalSource(self, interface):
455 return self.GetJinjaExportsForInterface(interface)
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
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
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))
478 for enum in self.module.enums:
479 self.Write(self.GenerateEnumSource(enum),
480 '%s.java' % GetNameForElement(enum))
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))
488 if self.module.constants:
489 self.Write(self.GenerateConstantsSource(self.module),
490 '%s.java' % GetConstantsMainEntityName(self.module))
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('.', '/')
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)
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();
512 def GetJinjaParameters(self):
513 return {
514 'lstrip_blocks': True,
515 'trim_blocks': True,
518 def GetGlobals(self):
519 return {
520 'namespace': self.module.namespace,
521 'module': self.module,