| #!/usr/bin/env python3 |
| # Copyright (C) 2026 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import argparse |
| import base64 |
| import os |
| import textwrap |
| |
| # The JVM specification strictly limits the bytecode of any single method (including |
| # static initializers `<clinit>`) to 65535 bytes. A simple `{ (byte)x, ... }` byte array |
| # generates push and store bytecode instructions per element. While the absolute |
| # mathematical threshold for standard javac is ~10,943 elements, we conservatively cap |
| # this at 8192 (8 KB) to guarantee safety across different compiler versions and pipelines. |
| JAVA_ARRAY_BYTE_LIMIT = 8192 |
| |
| TEMPLATE = """/* |
| * Copyright (C) 2026 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package {package}; |
| |
| /** |
| * Autogenerated by java_blob_emitter.py |
| */ |
| public class {classname} {{ |
| {class_body} |
| }} |
| """ |
| |
| |
| def generate_code(package: str, classname: str, data: bytes) -> str: |
| # Store data as byte array literal |
| if len(data) <= JAVA_ARRAY_BYTE_LIMIT: |
| bytes = [f"(byte)0x{b:02x}" for b in data] |
| bytes_joined = ", ".join(bytes) |
| bytes_wrapped = textwrap.fill( |
| bytes_joined, |
| width=100, |
| initial_indent=" ", |
| subsequent_indent=" ") |
| |
| class_body = " private static final byte[] BYTES = {\n" |
| class_body += bytes_wrapped + "\n" |
| class_body += " };\n\n" |
| class_body += " public static byte[] getBytes() {\n" |
| class_body += " return BYTES;\n" |
| class_body += " }" |
| # Store data as base64-encoded string |
| else: |
| bytes_base64 = base64.b64encode(data).decode('utf-8') |
| class_body = f" private static final String BYTES_BASE64 = \"{bytes_base64}\";\n\n" |
| class_body += " public static byte[] getBytes() {\n" |
| class_body += " return android.util.Base64.decode(BYTES_BASE64, android.util.Base64.DEFAULT);\n" |
| class_body += " }" |
| |
| return TEMPLATE.format( |
| package=package, classname=classname, class_body=class_body) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--output', required=True) |
| parser.add_argument('--package', required=True) |
| parser.add_argument('input') |
| args = parser.parse_args() |
| |
| with open(args.input, 'rb') as f: |
| data = f.read() |
| |
| classname = os.path.basename(args.output).split('.', 1)[0] |
| code = generate_code(args.package, classname, data) |
| |
| with open(args.output, 'w') as f: |
| f.write(code) |
| |
| |
| if __name__ == '__main__': |
| main() |