"""Generates a stripped down version of a java factory file.
A stripped down factory file is required in a feature's public_java target
during the compilation process so that features can depend on each other
without creating circular dependencies.
Afterwards, the stripped down factory's .class file is excluded from the
resulting target. The real factory uses the feature's internal implementations,
which is why it is not included in the feature's public_java target.
This script generates a stripped down factory file from real factory file to
reduce the burden of maintenance. The stripped down factory will have dummy
implementations of all public methods of the real factory.
This script requires that the real factory file has exactly one top-level class.
"""
import argparse
import datetime
import sys
import os
sys.path.append(
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'build'))
import action_helpers
sys.path.insert(
1,
os.path.join(
os.path.dirname(__file__), os.pardir, os.pardir, os.pardir,
'third_party', 'six', 'src'))
sys.path.insert(
1,
os.path.join(
os.path.dirname(__file__), os.pardir, os.pardir, os.pardir,
'third_party', 'javalang', 'src'))
import javalang
_PARAM_TEMPLATE = '{TYPE} {NAME}'
_METHOD_TEMPLATE = ('{MODIFIERS} {RETURN_TYPE} {NAME} ({PARAMS}) '
'{{ return {RETURN_VAL}; }}')
_FILE_TEMPLATE = '''\
// Copyright {YEAR} The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This file is autogenerated by
// {SCRIPT_NAME}
// Please do not change its content or use it in actual code ({DNS}).
package {PACKAGE};
{IMPORTS}
{MODIFIERS} class {CLASS_NAME} {{
{METHODS}
}}
'''
def _GetScriptName():
script_components = os.path.abspath(__file__).split(os.path.sep)
chrome_index = 0
for idx, value in enumerate(script_components):
if value == 'chrome':
chrome_index = idx
break
return os.sep.join(script_components[chrome_index:])
def _GetDefaultReturnVal(type_name):
if type_name in ('byte', 'short', 'int', 'long', 'float', 'double'):
return '0'
elif type_name == 'boolean':
return 'false'
elif type_name == 'void':
return ''
else:
return 'null'
def _ParseImports(imports):
"""Returns dict mapping from type name to import path."""
import_dict = {}
for import_ in imports:
if import_.static:
continue
assert not import_.wildcard
name = import_.path.split('.')[-1]
import_dict[name] = import_.path
return import_dict
def _ParsePublicMethodsSignatureTypes(clazz):
"""Returns set of type names used in the signatures of all public methods of
the given class.
"""
types = set()
for method in clazz.methods:
if 'public' in method.modifiers:
for p in method.parameters:
types.update(_GetNames(p.type))
types.update(_GetNames(method.return_type))
return types
def _GetNames(type_node):
if type_node is None:
return []
if isinstance(type_node, javalang.tree.ReferenceType):
names = [type_node.name]
if type_node.arguments:
for arg in type_node.arguments:
names.extend(_GetNames(arg))
return names
if isinstance(type_node, javalang.tree.TypeArgument):
return _GetNames(type_node.type)
if isinstance(type_node, javalang.tree.BasicType):
return [type_node.name]
assert False, 'Unknown type_node={}'.format(type_node)
def _FormatType(type_node):
if type_node is None:
return 'void'
if isinstance(type_node, javalang.tree.ReferenceType):
if not type_node.arguments:
return type_node.name
formatted_args = (_FormatType(arg) for arg in type_node.arguments)
return '{name}<{arguments}>'.format(name=type_node.name,
arguments=','.join(formatted_args))
if isinstance(type_node, javalang.tree.TypeArgument):
return _FormatType(type_node.type)
if isinstance(type_node, javalang.tree.BasicType):
return type_node.name
assert False, 'Type node {node} cannot be formatted.'.format(node=type_node)
def _FormatMethodModifiers(method_modifiers):
modifiers_in_order = ('public', 'protected', 'private', 'abstract', 'static',
'final', 'synchronized', 'native', 'strictfp')
unknown_modifiers = method_modifiers - set(modifiers_in_order)
assert len(unknown_modifiers) == 0, (
f'Unknown method modifiers: {unknown_modifiers}')
return ' '.join([m for m in modifiers_in_order if m in method_modifiers])
def _FormatMethod(method):
params = []
for param in method.parameters:
param_dict = {
'TYPE': _FormatType(param.type),
'NAME': param.name,
}
params.append(_PARAM_TEMPLATE.format(**param_dict))
return_type = _FormatType(method.return_type)
method_dict = {
'MODIFIERS': _FormatMethodModifiers(method.modifiers),
'RETURN_TYPE': return_type,
'NAME': method.name,
'PARAMS': ', '.join(params),
'RETURN_VAL': _GetDefaultReturnVal(return_type),
}
return (_METHOD_TEMPLATE.format(**method_dict))
def _FormatPublicMethods(clazz):
methods = []
for method in clazz.methods:
if 'public' in method.modifiers:
methods.append(_FormatMethod(method))
return methods
def _FilterAndFormatImports(import_dict, signature_types):
"""Returns formatted imports required by the passed signature types."""
formatted_imports = [
'import %s;' % import_dict[t] for t in signature_types if t in import_dict
]
return sorted(formatted_imports)
def main(args):
parser = argparse.ArgumentParser()
parser.add_argument('--input', required=True, help='Input java file path.')
parser.add_argument('--output', required=True, help='Output java file path.')
options = parser.parse_args(args)
with open(options.input, 'r') as f:
content = f.read()
if '<@Nullable' in content:
sys.stderr.write("""
Error: @Nullable annotations inside generic types are not supported.
Please remove them from {file_path}.
See https://crbug.com/433562519 for details.
""".format(file_path=options.input))
sys.exit(1)
java_ast = javalang.parse.parse(content)
assert len(java_ast.types) == 1, 'Can only process Java files with one class'
clazz = java_ast.types[0]
import_dict = _ParseImports(java_ast.imports)
signature_types = _ParsePublicMethodsSignatureTypes(clazz)
formatted_public_methods = _FormatPublicMethods(clazz)
formatted_imports = _FilterAndFormatImports(import_dict, signature_types)
file_dict = {
'DNS': ' '.join(['DO', 'NOT', 'SUBMIT']),
'YEAR': str(datetime.date.today().year),
'SCRIPT_NAME': _GetScriptName(),
'PACKAGE': java_ast.package.name,
'IMPORTS': '\n'.join(formatted_imports),
'MODIFIERS': ' '.join(clazz.modifiers),
'CLASS_NAME': clazz.name,
'METHODS': '\n'.join([' ' + m for m in formatted_public_methods])
}
with action_helpers.atomic_output(options.output, mode='w') as f:
f.write(_FILE_TEMPLATE.format(**file_dict))
if __name__ == '__main__':
main(sys.argv[1:])