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

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

Issue 814543006: Move //mojo/{public, edk} underneath //third_party (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase Created 5 years, 11 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 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, with_generics=True):
238 unboxed_type = GetJavaType(context, kind, False, with_generics)
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, with_generics=True):
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 if with_generics:
254 return 'java.util.Map<%s, %s>' % (
255 GetBoxedJavaType(context, kind.key_kind),
256 GetBoxedJavaType(context, kind.value_kind))
257 else:
258 return 'java.util.Map'
259 if mojom.IsArrayKind(kind):
260 return '%s[]' % GetJavaType(context, kind.kind, boxed, with_generics)
261 if mojom.IsEnumKind(kind):
262 return 'int'
263 return _spec_to_java_type[kind.spec]
264
265 @contextfilter
266 def DefaultValue(context, field):
267 assert field.default
268 if isinstance(field.kind, mojom.Struct):
269 assert field.default == 'default'
270 return 'new %s()' % GetJavaType(context, field.kind)
271 return '(%s) %s' % (
272 GetJavaType(context, field.kind),
273 ExpressionToText(context, field.default, kind_spec=field.kind.spec))
274
275 @contextfilter
276 def ConstantValue(context, constant):
277 return '(%s) %s' % (
278 GetJavaType(context, constant.kind),
279 ExpressionToText(context, constant.value, kind_spec=constant.kind.spec))
280
281 @contextfilter
282 def NewArray(context, kind, size):
283 if mojom.IsArrayKind(kind.kind):
284 return NewArray(context, kind.kind, size) + '[]'
285 return 'new %s[%s]' % (
286 GetJavaType(context, kind.kind, boxed=False, with_generics=False), size)
287
288 @contextfilter
289 def ExpressionToText(context, token, kind_spec=''):
290 def _TranslateNamedValue(named_value):
291 entity_name = GetNameForElement(named_value)
292 if named_value.parent_kind:
293 return GetJavaType(context, named_value.parent_kind) + '.' + entity_name
294 # Handle the case where named_value is a module level constant:
295 if not isinstance(named_value, mojom.EnumValue):
296 entity_name = (GetConstantsMainEntityName(named_value.module) + '.' +
297 entity_name)
298 if GetPackage(named_value.module) == GetPackage(context.resolve('module')):
299 return entity_name
300 return GetPackage(named_value.module) + '.' + entity_name
301
302 if isinstance(token, mojom.NamedValue):
303 return _TranslateNamedValue(token)
304 if kind_spec.startswith('i') or kind_spec.startswith('u'):
305 # Add Long suffix to all integer literals.
306 number = ast.literal_eval(token.lstrip('+ '))
307 if not isinstance(number, (int, long)):
308 raise ValueError('got unexpected type %r for int literal %r' % (
309 type(number), token))
310 # If the literal is too large to fit a signed long, convert it to the
311 # equivalent signed long.
312 if number >= 2 ** 63:
313 number -= 2 ** 64
314 return '%dL' % number
315 if isinstance(token, mojom.BuiltinValue):
316 if token.value == 'double.INFINITY':
317 return 'java.lang.Double.POSITIVE_INFINITY'
318 if token.value == 'double.NEGATIVE_INFINITY':
319 return 'java.lang.Double.NEGATIVE_INFINITY'
320 if token.value == 'double.NAN':
321 return 'java.lang.Double.NaN'
322 if token.value == 'float.INFINITY':
323 return 'java.lang.Float.POSITIVE_INFINITY'
324 if token.value == 'float.NEGATIVE_INFINITY':
325 return 'java.lang.Float.NEGATIVE_INFINITY'
326 if token.value == 'float.NAN':
327 return 'java.lang.Float.NaN'
328 return token
329
330 def GetArrayKind(kind, size = None):
331 if size is None:
332 return mojom.Array(kind)
333 else:
334 array = mojom.Array(kind, 0)
335 array.java_map_size = size
336 return array
337
338 def GetArrayExpectedLength(kind):
339 if mojom.IsArrayKind(kind) and kind.length is not None:
340 return getattr(kind, 'java_map_size', str(kind.length))
341 else:
342 return 'org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH'
343
344 def IsPointerArrayKind(kind):
345 if not mojom.IsArrayKind(kind):
346 return False
347 sub_kind = kind.kind
348 return mojom.IsObjectKind(sub_kind)
349
350 def GetResponseStructFromMethod(method):
351 return generator.GetDataHeader(
352 False, generator.GetResponseStructFromMethod(method))
353
354 def GetStructFromMethod(method):
355 return generator.GetDataHeader(
356 False, generator.GetStructFromMethod(method))
357
358 def GetConstantsMainEntityName(module):
359 if 'JavaConstantsClassName' in module.attributes:
360 return ParseStringAttribute(module.attributes['JavaConstantsClassName'])
361 # This constructs the name of the embedding classes for module level constants
362 # by extracting the mojom's filename and prepending it to Constants.
363 return (UpperCamelCase(module.path.split('/')[-1].rsplit('.', 1)[0]) +
364 'Constants')
365
366 def GetMethodOrdinalName(method):
367 return ConstantStyle(method.name) + '_ORDINAL'
368
369 def HasMethodWithResponse(interface):
370 for method in interface.methods:
371 if method.response_parameters is not None:
372 return True
373 return False
374
375 def HasMethodWithoutResponse(interface):
376 for method in interface.methods:
377 if method.response_parameters is None:
378 return True
379 return False
380
381 @contextlib.contextmanager
382 def TempDir():
383 dirname = tempfile.mkdtemp()
384 try:
385 yield dirname
386 finally:
387 shutil.rmtree(dirname)
388
389 def ZipContentInto(root, zip_filename):
390 with zipfile.ZipFile(zip_filename, 'w') as zip_file:
391 for dirname, _, files in os.walk(root):
392 for filename in files:
393 path = os.path.join(dirname, filename)
394 path_in_archive = os.path.relpath(path, root)
395 zip_file.write(path, path_in_archive)
396
397 class Generator(generator.Generator):
398
399 java_filters = {
400 'array_expected_length': GetArrayExpectedLength,
401 'array': GetArrayKind,
402 'constant_value': ConstantValue,
403 'decode_method': DecodeMethod,
404 'default_value': DefaultValue,
405 'encode_method': EncodeMethod,
406 'expression_to_text': ExpressionToText,
407 'has_method_without_response': HasMethodWithoutResponse,
408 'has_method_with_response': HasMethodWithResponse,
409 'interface_response_name': GetInterfaceResponseName,
410 'is_array_kind': mojom.IsArrayKind,
411 'is_handle': mojom.IsNonInterfaceHandleKind,
412 'is_map_kind': mojom.IsMapKind,
413 'is_nullable_kind': mojom.IsNullableKind,
414 'is_pointer_array_kind': IsPointerArrayKind,
415 'is_reference_kind': mojom.IsReferenceKind,
416 'is_struct_kind': mojom.IsStructKind,
417 'java_true_false': GetJavaTrueFalse,
418 'java_type': GetJavaType,
419 'method_ordinal_name': GetMethodOrdinalName,
420 'name': GetNameForElement,
421 'new_array': NewArray,
422 'response_struct_from_method': GetResponseStructFromMethod,
423 'struct_from_method': GetStructFromMethod,
424 'struct_size': lambda ps: ps.GetTotalSize() + _HEADER_SIZE,
425 }
426
427 def GetJinjaExports(self):
428 return {
429 'package': GetPackage(self.module),
430 }
431
432 def GetJinjaExportsForInterface(self, interface):
433 exports = self.GetJinjaExports()
434 exports.update({'interface': interface})
435 if interface.client:
436 all_interfaces = [] + self.module.interfaces
437 for each in self.module.imports:
438 all_interfaces += each['module'].interfaces
439 interfaces_by_name = dict((x.name, x) for x in all_interfaces)
440 assert interface.client in interfaces_by_name, (
441 'Unable to find interface %s declared as client of %s.' %
442 (interface.client, interface.name))
443 exports.update({'client': interfaces_by_name[interface.client]})
444 return exports
445
446 @UseJinja('java_templates/enum.java.tmpl', filters=java_filters)
447 def GenerateEnumSource(self, enum):
448 exports = self.GetJinjaExports()
449 exports.update({'enum': enum})
450 return exports
451
452 @UseJinja('java_templates/struct.java.tmpl', filters=java_filters)
453 def GenerateStructSource(self, struct):
454 exports = self.GetJinjaExports()
455 exports.update({'struct': struct})
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 if not os.path.exists(self.output_dir):
475 try:
476 os.makedirs(self.output_dir)
477 except:
478 # Ignore errors on directory creation.
479 pass
480
481 # Keep this above the others as .GetStructs() changes the state of the
482 # module, annotating structs with required information.
483 for struct in self.GetStructs():
484 self.Write(self.GenerateStructSource(struct),
485 '%s.java' % GetNameForElement(struct))
486
487 for enum in self.module.enums:
488 self.Write(self.GenerateEnumSource(enum),
489 '%s.java' % GetNameForElement(enum))
490
491 for interface in self.module.interfaces:
492 self.Write(self.GenerateInterfaceSource(interface),
493 '%s.java' % GetNameForElement(interface))
494 self.Write(self.GenerateInterfaceInternalSource(interface),
495 '%s_Internal.java' % GetNameForElement(interface))
496
497 if self.module.constants:
498 self.Write(self.GenerateConstantsSource(self.module),
499 '%s.java' % GetConstantsMainEntityName(self.module))
500
501 def GenerateFiles(self, unparsed_args):
502 parser = argparse.ArgumentParser()
503 parser.add_argument('--java_output_directory', dest='java_output_directory')
504 args = parser.parse_args(unparsed_args)
505 package_path = GetPackage(self.module).replace('.', '/')
506
507 # Generate the java files in a temporary directory and place a single
508 # srcjar in the output directory.
509 basename = self.MatchMojomFilePath("%s.srcjar" % self.module.name)
510 zip_filename = os.path.join(self.output_dir, basename)
511 with TempDir() as temp_java_root:
512 self.output_dir = os.path.join(temp_java_root, package_path)
513 self.DoGenerateFiles();
514 ZipContentInto(temp_java_root, zip_filename)
515
516 if args.java_output_directory:
517 # If requested, generate the java files directly into indicated directory.
518 self.output_dir = os.path.join(args.java_output_directory, package_path)
519 self.DoGenerateFiles();
520
521 def GetJinjaParameters(self):
522 return {
523 'lstrip_blocks': True,
524 'trim_blocks': True,
525 }
526
527 def GetGlobals(self):
528 return {
529 'namespace': self.module.namespace,
530 'module': self.module,
531 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698