| # Copyright 2023 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import java_types |
| import common |
| |
| |
| class _Context: |
| |
| def __init__(self, jni_obj, gen_jni_class, script_name, per_file_natives): |
| self.jni_obj = jni_obj |
| self.gen_jni_class = gen_jni_class |
| self.script_name = script_name |
| self.per_file_natives = per_file_natives |
| |
| self.interface_name = jni_obj.proxy_interface.name_with_dots |
| self.proxy_class = java_types.JavaClass( |
| f'{self.jni_obj.java_class.full_name_with_slashes}Jni') |
| self.type_resolver = java_types.TypeResolver(self.proxy_class) |
| self.type_resolver.imports = jni_obj.GetClassesToBeImported() |
| |
| |
| def _implicit_array_class_param(native, type_resolver): |
| return_type = native.return_type |
| class_name = return_type.to_array_element_type().to_java(type_resolver) |
| return class_name + '.class' |
| |
| |
| def _proxy_method(sb, ctx, native, method_fqn): |
| return_type_str = native.return_type.to_java(ctx.type_resolver) |
| sig_params = native.params.to_java_declaration(ctx.type_resolver) |
| |
| sb(f""" |
| @Override |
| public {return_type_str} {native.name}({sig_params})""") |
| with sb.block(): |
| if native.first_param_cpp_type: |
| sb(f'assert {native.params[0].name} != 0;\n') |
| for p in native.params: |
| if not p.java_type.nullable: |
| sb(f'assert {p.name} != null;\n') |
| with sb.statement(): |
| if not native.return_type.is_void(): |
| sb(f'return ({return_type_str}) ') |
| sb(method_fqn) |
| with sb.param_list() as plist: |
| plist.extend(p.name for p in native.params) |
| if native.needs_implicit_array_element_class_param: |
| plist.append(_implicit_array_class_param(native, ctx.type_resolver)) |
| |
| |
| def _native_method(sb, native, method_fqn): |
| params = native.proxy_params.to_java_declaration() |
| return_type = native.proxy_return_type.to_java() |
| sb(f'private static native {return_type} {method_fqn}({params});\n') |
| |
| |
| def _get_method(sb, ctx): |
| sb(f'\npublic static {ctx.interface_name} get()') |
| with sb.block(): |
| if not ctx.per_file_natives: |
| sb(f"""\ |
| if ({ctx.gen_jni_class.name}.TESTING_ENABLED) {{ |
| if (testInstance != null) {{ |
| return testInstance; |
| }} |
| if ({ctx.gen_jni_class.name}.REQUIRE_MOCK) {{ |
| throw new UnsupportedOperationException( |
| "No mock found for the native implementation of {ctx.interface_name}. " |
| + "The current configuration requires implementations be mocked."); |
| }} |
| }} |
| """) |
| sb(f"""\ |
| NativeLibraryLoadedStatus.checkLoaded(); |
| return new {ctx.proxy_class.name}(); |
| """) |
| |
| |
| def _class_body(sb, ctx): |
| sb(f"""\ |
| private static {ctx.interface_name} testInstance; |
| |
| public static final JniStaticTestMocker<{ctx.interface_name}> TEST_HOOKS = |
| new JniStaticTestMocker<{ctx.interface_name}>()""") |
| with sb.block(end='};\n'): |
| sb(f"""\ |
| @Override |
| public void setInstanceForTesting({ctx.interface_name} instance)""") |
| with sb.block(): |
| if not ctx.per_file_natives: |
| sb(f"""\ |
| if (!{ctx.gen_jni_class.name}.TESTING_ENABLED) {{ |
| throw new RuntimeException( |
| "Tried to set a JNI mock when mocks aren't enabled!"); |
| }} |
| """) |
| sb('testInstance = instance;\n') |
| |
| for native in ctx.jni_obj.proxy_natives: |
| if ctx.per_file_natives: |
| method_fqn = f'native{common.capitalize(native.name)}' |
| else: |
| method_fqn = f'{ctx.gen_jni_class.name}.{native.proxy_name}' |
| |
| _proxy_method(sb, ctx, native, method_fqn) |
| if ctx.per_file_natives: |
| _native_method(sb, native, method_fqn) |
| |
| _get_method(sb, ctx) |
| |
| |
| def _imports(sb, ctx): |
| classes = { |
| 'org.jni_zero.CheckDiscard', |
| 'org.jni_zero.JniStaticTestMocker', |
| 'org.jni_zero.NativeLibraryLoadedStatus', |
| } |
| if not ctx.per_file_natives: |
| classes.add(ctx.gen_jni_class.full_name_with_dots) |
| |
| for c in ctx.type_resolver.imports: |
| # Since this is pure Java, the class generated here will go through jarjar |
| # and thus we want to avoid prefixes. |
| c = c.class_without_prefix |
| if c.is_nested: |
| # We will refer to all nested classes by OuterClass.InnerClass. We do this |
| # to reduce risk of naming collisions. |
| c = c.get_outer_class() |
| classes.add(c.full_name_with_dots) |
| |
| for c in sorted(classes): |
| sb(f'import {c};\n') |
| |
| |
| def Generate(jni_obj, *, gen_jni_class, script_name, per_file_natives=False): |
| ctx = _Context(jni_obj, gen_jni_class, script_name, per_file_natives) |
| |
| sb = common.StringBuilder() |
| sb(f"""\ |
| // |
| // This file was generated by {script_name} |
| // |
| package {jni_obj.java_class.class_without_prefix.package_with_dots}; |
| |
| """) |
| _imports(sb, ctx) |
| sb('\n') |
| |
| visibility = 'public ' if jni_obj.proxy_visibility == 'public' else '' |
| class_name = ctx.proxy_class.name |
| if not per_file_natives: |
| sb('@CheckDiscard("crbug.com/993421")\n') |
| sb(f'{visibility}class {class_name} implements {ctx.interface_name}') |
| with sb.block(): |
| _class_body(sb, ctx) |
| return sb.to_string() |