blob: 92df5629abc11c57cc538a466eb8bc9c19a56db9 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""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__),
os.pardir, os.pardir, os.pardir,
'build', 'android', 'gyp'))
from util import build_utils
sys.path.append(
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. All rights reserved.
// 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.
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:
types.update([p.type.name for p in method.parameters])
# If return type is void return_type will be none.
if method.return_type:
types.add(method.return_type.name)
return types
def _FormatMethod(method):
params = []
for param in method.parameters:
param_dict = {
'TYPE': param.type.name,
'NAME': param.name,
}
params.append(_PARAM_TEMPLATE.format(**param_dict))
return_type = method.return_type.name if method.return_type else 'void'
method_dict = {
'MODIFIERS': ' '.join(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()
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 = {
'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 build_utils.AtomicOutput(options.output) as f:
f.write(_FILE_TEMPLATE.format(**file_dict))
if __name__ == '__main__':
main(sys.argv[1:])