Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(296)

Side by Side Diff: mojo/public/tools/bindings/generators/mojom_java_generator.py

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

Powered by Google App Engine
This is Rietveld 408576698