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' 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
,
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
,
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
)
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
154 assert mojom
.IsArrayKind(kind
)
156 'org.chromium.mojo.bindings.BindingsHelper.ARRAY_NULLABLE'
158 'org.chromium.mojo.bindings.BindingsHelper.ELEMENT_NULLABLE'
160 'org.chromium.mojo.bindings.BindingsHelper.NOTHING_NULLABLE'
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
)
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
))
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
))
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
))
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'])
219 return 'org.chromium.mojom.' + module
.namespace
220 return 'org.chromium.mojom'
222 def GetNameForKind(context
, kind
):
223 def _GetNameHierachy(kind
):
226 hierachy
= _GetNameHierachy(kind
.parent_kind
)
227 hierachy
.append(GetNameForElement(kind
))
230 module
= context
.resolve('module')
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
]
244 def GetJavaType(context
, kind
, boxed
=False):
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
):
260 return _spec_to_java_type
[kind
.spec
]
263 def DefaultValue(context
, field
):
265 if isinstance(field
.kind
, mojom
.Struct
):
266 assert field
.default
== 'default'
267 return 'new %s()' % GetJavaType(context
, field
.kind
)
269 GetJavaType(context
, field
.kind
),
270 ExpressionToText(context
, field
.default
, kind_spec
=field
.kind
.spec
))
273 def ConstantValue(context
, constant
):
275 GetJavaType(context
, constant
.kind
),
276 ExpressionToText(context
, constant
.value
, kind_spec
=constant
.kind
.spec
))
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
)
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
) + '.' +
294 if GetPackage(named_value
.module
) == GetPackage(context
.resolve('module')):
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:
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'
326 def GetArrayKind(kind
, size
= None):
328 return mojom
.Array(kind
)
330 array
= mojom
.Array(kind
, 0)
331 array
.java_map_size
= size
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
))
338 return 'org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH'
340 def IsPointerArrayKind(kind
):
341 if not mojom
.IsArrayKind(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]) +
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:
371 def HasMethodWithoutResponse(interface
):
372 for method
in interface
.methods
:
373 if method
.response_parameters
is None:
377 @contextlib.contextmanager
379 dirname
= tempfile
.mkdtemp()
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
):
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
):
425 'package': GetPackage(self
.module
),
428 def GetJinjaExportsForInterface(self
, interface
):
429 exports
= self
.GetJinjaExports()
430 exports
.update({'interface': interface
})
432 for client
in self
.module
.interfaces
:
433 if client
.name
== interface
.client
:
434 exports
.update({'client': client
})
437 @UseJinja('java_templates/enum.java.tmpl', filters
=java_filters
)
438 def GenerateEnumSource(self
, enum
):
439 exports
= self
.GetJinjaExports()
440 exports
.update({'enum': enum
})
443 @UseJinja('java_templates/struct.java.tmpl', filters
=java_filters
)
444 def GenerateStructSource(self
, struct
):
445 exports
= self
.GetJinjaExports()
446 exports
.update({'struct': struct
})
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
})
464 def DoGenerateFiles(self
):
465 if not os
.path
.exists(self
.output_dir
):
467 os
.makedirs(self
.output_dir
)
469 # Ignore errors on directory creation.
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
):
514 'lstrip_blocks': True,
518 def GetGlobals(self
):
520 'namespace': self
.module
.namespace
,
521 'module': self
.module
,