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."""
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'
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 = {
93 def NameToComponent(name):
94 # insert '_' between anything and a Title name (e.g, HTTPEntry2FooBar ->
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)])
106 uccc = UpperCamelCase(name)
107 return uccc[0].lower() + uccc[1:]
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])
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,
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,
130 return ConstantStyle(element.name)
131 raise Exception('Unexpected element: ' % element)
133 def GetInterfaceResponseName(method):
134 return UpperCamelCase(method.name + 'Response')
136 def ParseStringAttribute(attribute):
137 assert isinstance(attribute, basestring)
140 def GetJavaTrueFalse(value):
141 return 'true' if value else 'false'
143 def GetArrayNullabilityFlags(kind):
144 """Returns nullability flags for an array type, see Decoder.java.
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
150 assert mojom.IsAnyArrayKind(kind)
152 'org.chromium.mojo.bindings.BindingsHelper.ARRAY_NULLABLE'
154 'org.chromium.mojo.bindings.BindingsHelper.ELEMENT_NULLABLE'
156 'org.chromium.mojo.bindings.BindingsHelper.NOTHING_NULLABLE'
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)
165 flags_to_set = [NOTHING_NULLABLE]
166 return ' | '.join(flags_to_set)
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))
178 params.append(GetJavaTrueFalse(mojom.IsNullableKind(kind)))
179 if mojom.IsAnyArrayKind(kind):
180 if mojom.IsFixedArrayKind(kind):
181 params.append(str(kind.length))
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))
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))
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'])
218 return 'org.chromium.mojom.' + module.namespace
220 def GetNameForKind(context, kind):
221 def _GetNameHierachy(kind):
224 hierachy = _GetNameHierachy(kind.parent_kind)
225 hierachy.append(GetNameForElement(kind))
228 module = context.resolve('module')
230 if GetPackage(module) != GetPackage(kind.module):
231 elements += [GetPackage(kind.module)]
232 elements += _GetNameHierachy(kind)
233 return '.'.join(elements)
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]
242 def GetJavaType(context, kind, boxed=False):
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):
254 return _spec_to_java_type[kind.spec]
257 def DefaultValue(context, field):
259 if isinstance(field.kind, mojom.Struct):
260 assert field.default == 'default'
261 return 'new %s()' % GetJavaType(context, field.kind)
263 GetJavaType(context, field.kind),
264 ExpressionToText(context, field.default, kind_spec=field.kind.spec))
267 def ConstantValue(context, constant):
269 GetJavaType(context, constant.kind),
270 ExpressionToText(context, constant.value, kind_spec=constant.kind.spec))
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)
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) + '.' +
288 if GetPackage(named_value.module) == GetPackage(context.resolve('module')):
290 return GetPackage(named_value.module) + '.' + entity_name
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:
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'
320 def IsPointerArrayKind(kind):
321 if not mojom.IsAnyArrayKind(kind):
324 return mojom.IsObjectKind(sub_kind)
326 def GetResponseStructFromMethod(method):
327 return generator.GetDataHeader(
328 False, generator.GetResponseStructFromMethod(method))
330 def GetStructFromMethod(method):
331 return generator.GetDataHeader(
332 False, generator.GetStructFromMethod(method))
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]) +
342 def GetMethodOrdinalName(method):
343 return ConstantStyle(method.name) + '_ORDINAL'
345 def HasMethodWithResponse(interface):
346 for method in interface.methods:
347 if method.response_parameters:
351 def HasMethodWithoutResponse(interface):
352 for method in interface.methods:
353 if not method.response_parameters:
357 @contextlib.contextmanager
359 dirname = tempfile.mkdtemp()
363 shutil.rmtree(dirname)
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)
373 class Generator(generator.Generator):
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,
399 def GetJinjaExports(self):
401 'package': GetPackage(self.module),
404 def GetJinjaExportsForInterface(self, interface):
405 exports = self.GetJinjaExports()
406 exports.update({'interface': interface})
408 for client in self.module.interfaces:
409 if client.name == interface.client:
410 exports.update({'client': client})
413 @UseJinja('java_templates/enum.java.tmpl', filters=java_filters)
414 def GenerateEnumSource(self, enum):
415 exports = self.GetJinjaExports()
416 exports.update({'enum': enum})
419 @UseJinja('java_templates/struct.java.tmpl', filters=java_filters)
420 def GenerateStructSource(self, struct):
421 exports = self.GetJinjaExports()
422 exports.update({'struct': struct})
425 @UseJinja('java_templates/interface.java.tmpl', filters=java_filters)
426 def GenerateInterfaceSource(self, interface):
427 return self.GetJinjaExportsForInterface(interface)
429 @UseJinja('java_templates/interface_internal.java.tmpl', filters=java_filters)
430 def GenerateInterfaceInternalSource(self, interface):
431 return self.GetJinjaExportsForInterface(interface)
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})
440 def DoGenerateFiles(self):
441 if not os.path.exists(self.output_dir):
443 os.makedirs(self.output_dir)
445 # Ignore errors on directory creation.
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))
454 for enum in self.module.enums:
455 self.Write(self.GenerateEnumSource(enum),
456 '%s.java' % GetNameForElement(enum))
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))
464 if self.module.constants:
465 self.Write(self.GenerateConstantsSource(self.module),
466 '%s.java' % GetConstantsMainEntityName(self.module))
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('.', '/')
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)
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();
488 def GetJinjaParameters(self):
490 'lstrip_blocks': True,
494 def GetGlobals(self):
496 'namespace': self.module.namespace,
497 'module': self.module,