diff --git a/.gitignore b/.gitignore index 9382be6..7d64e54 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ -*.java *.class .idea/ __pycache__/ *.pyc venv/ +/tdlib-serializer.iml +*.j +/out/ diff --git a/__main__.py b/__main__.py deleted file mode 100644 index 1e517c7..0000000 --- a/__main__.py +++ /dev/null @@ -1,943 +0,0 @@ -import sys -import typing -import re - -from collections import OrderedDict -from code_writer import CodeWriter - - -natives = ["int", "long", "boolean", "String", "double", "byte[]", "byte"] - -native_to_object = { - "boolean": "Boolean", - "byte[]": "Byte", - "byte": "Byte", - "int": "Integer", - "short": "Short", - "char": "Character", - "long": "Long", - "float": "Float", - "double": "Double", - "String": "String" -} - -comment = ("/**", "* ", "*/") - -cmp_natives = natives.copy() -cmp_natives.remove("String") -cmp_natives.remove("byte[]") - - -def chunker(seq, size): - return (seq[pos:pos + size] for pos in range(0, len(seq), size)) - - -def split_docs(docs: typing.List[str]) -> typing.List[str]: - tokens = " ".join(docs).split(" ") - result = [tokens[0]] - - for token in tokens[1:]: - if len(" ".join(result[-1]) + token) < 70: - result[-1] += f" {token}" - else: - result.append(token) - - if len(result) > 1 and len(result[-1].split(" ")) < 3: - result[-2] += f" {result[-1]}" - del result[-1] - - return result - - -def extract_doc(lines: typing.List[str], line: int) -> typing.List[str]: - look_back = 2 - line = next( - n - for n in range(line - look_back, 0, -1) - if lines[n].startswith("/**") - ) - - result = [] - - while True: - line += 1 - - if lines[line].startswith("*/"): - break - - current = lines[line].split(" ", maxsplit=1)[-1] - - if current.strip() != "*": - result.append(current) - - return result - - -def hash_object(name: str, typ: str) -> str: - if not typ.endswith("[]"): - return f"{name} == null ? 0 : {name}.hashCode()" - - elif typ.endswith("[][]"): - return f"Arrays.deepHashCode({name})" - - else: - return f"Arrays.hashCode({name})" - - -def deserialize_tdapi(output: CodeWriter, arg_name: str, arg_type: str, cont, classes, null_check: bool = True): - if null_check: - output.indent() - output.open_if("input.readBoolean()") - output.newline() - - output.indent() - - if arg_type in cont: - output.open_switch("input.readInt()") - output.newline() - - for class_name, class_meta in classes.items(): - if class_meta[1] == arg_type and class_name != arg_type: - output.indent() - output.open_switch_case(f"{class_name}.CONSTRUCTOR") - - output.newline() - output.indent() - output.class_assign(arg_name, f"new {class_name}(input)") - - output.newline() - output.indent() - output.switch_break() - output.newline() - - output.indent() - output.open_switch_default() - output.newline() - - output.indent() - output.exception("UnsupportedOperationException") - output.newline() - output.indent_depth -= 1 - - output.close_block(space=True) - - else: - output.open_if(f"{arg_type}.CONSTRUCTOR != input.readInt()") - - output.newline() - output.indent() - output.exception("UnsupportedOperationException") - - output.newline() - output.close_block(space=True) - - output.indent() - output.class_assign(arg_name, f"new {arg_type}(input)") - output.newline() - - if null_check: - output.close_block(space=True) - - -def deserialize_native(output: CodeWriter, arg_name, arg_type, null_check: bool = True): - if arg_type == "int": - output.indent() - output.class_assign(arg_name, "input.readInt()") - output.newline() - - if arg_type == "byte[]" or arg_type == "byte": - if null_check: - output.indent() - output.open_if("input.readBoolean()") - output.newline() - - output.indent() - output.class_assign(arg_name, f"new byte[input.readInt()]") - output.newline() - - output.indent() - output.call("input.readFully", f"this.{arg_name}") - output.newline() - - if null_check: - output.close_block(space=True) - - if arg_type == "long": - output.indent() - output.class_assign(arg_name, "input.readLong()") - output.newline() - - if arg_type == "double": - output.indent() - output.class_assign(arg_name, "input.readDouble()") - output.newline() - - if arg_type == "boolean": - output.indent() - output.class_assign(arg_name, "input.readBoolean()") - output.newline() - - if arg_type == "String": - if null_check: - output.indent() - output.open_if("input.readBoolean()") - output.newline() - - tmp_name = arg_name.split("[")[0] + "Tmp" - output.indent() - output.local_assign("byte[]", tmp_name, f"new byte[input.readInt()]") - output.newline() - - output.indent() - output.call("input.readFully", tmp_name) - output.newline() - - output.indent() - output.class_assign(arg_name, f"new String({tmp_name}, StandardCharsets.UTF_8)") - output.newline() - - if null_check: - output.close_block(space=True) - - -def serialize_tdapi(output: CodeWriter, arg_name, null_check: bool = True): - if null_check: - output.indent() - output.open_if(f"this.{arg_name} == null") - - output.newline() - output.indent() - output.call("output.writeBoolean", "false") - output.newline() - - output.open_if_else(space=True) - output.newline() - - output.indent() - output.call("output.writeBoolean", "true") - output.newline() - - output.indent() - output.call(f"this.{arg_name}.serialize", "output") - - output.newline() - - if null_check: - output.close_block(space=True) - - -def serialize_native(output: CodeWriter, arg_type: str, arg_name: str, null_check: bool = True): - if arg_type == "int": - output.indent() - output.call("output.writeInt", f"this.{arg_name}") - output.newline() - - if arg_type == "byte[]" or arg_type == "byte": - if null_check: - output.indent() - output.open_if(f"this.{arg_name} == null") - - output.newline() - output.indent() - output.call("output.writeBoolean", "false") - output.newline() - - output.open_if_else(space=True) - output.newline() - - output.indent() - output.call("output.writeBoolean", "true") - output.newline() - - output.indent() - output.call("output.writeInt", f"this.{arg_name}.length") - output.newline() - output.indent() - output.call("output.write", f"this.{arg_name}") - output.newline() - - if null_check: - output.close_block(space=True) - - if arg_type == "long": - output.indent() - output.call("output.writeLong", f"this.{arg_name}") - output.newline() - - if arg_type == "double": - output.indent() - output.call("output.writeDouble", f"this.{arg_name}") - output.newline() - - if arg_type == "boolean": - output.indent() - output.call("output.writeBoolean", f"this.{arg_name}") - output.newline() - - if arg_type == "String": - if null_check: - output.indent() - output.open_if(f"this.{arg_name} == null") - - output.newline() - output.indent() - output.call("output.writeBoolean", "false") - output.newline() - - output.open_if_else(space=True) - output.newline() - - output.indent() - output.call("output.writeBoolean", "true") - output.newline() - - output.indent() - tmp_name = arg_name.split("[")[0] + "Tmp" - output.local_assign("byte[]", tmp_name, f"this.{arg_name}.getBytes(StandardCharsets.UTF_8)") - output.newline() - output.indent() - output.call("output.writeInt", f"{tmp_name}.length") - output.newline() - output.indent() - output.call("output.write", tmp_name) - output.newline() - - if null_check: - output.close_block(space=True) - -def remove_parentheses(s): - return re.sub(r'\<[^<>]*\>', '', s) - - -def main(input_path: str, output_path: str, headers_path: str, java17: str): - data_input = open(input_path) - - java17 = java17 == "true" - - package: typing.Optional[str] = None - current_constructor: typing.Optional[int] = None - current_class_name: typing.Optional[str] = None - inside_abstract_class: bool = False - inside_object_class: bool = False - inside_function_class: bool = False - inside_object_container_class: bool = False - container_class_name: typing.Optional[str] = None - current_class_docs: typing.Optional[typing.List[str]] = None - function_depth: int = 0 - - function_classes = OrderedDict() - # key: {class_name, value: (constructor_id, container_name, [arg_type, arg_name], docs)} - - object_classes = OrderedDict() - # key: {class_name, value: (constructor_id, container_name, [arg_type, arg_name], docs)} - - container_classes: typing.List[str] = OrderedDict() - # key: {class_name, value: (docs)} - - current_arguments: typing.Optional[typing.List[typing.Tuple[str, str, typing.List[str]]]] = None - # [(arg_name, arg_type), ...] - - lines = list(map(remove_parentheses, map(str.strip, data_input.readlines()))) - - for no, line in enumerate(lines): - keywords = line.split() - - if not keywords: - continue - - if (inside_object_class or inside_function_class) and keywords[-1] == "{": - function_depth += 1 - continue - - if (inside_object_class or inside_function_class) and keywords[-1] == "}" and function_depth > 0: - function_depth -= 1 - continue - - if function_depth > 0: - continue - - if inside_object_container_class and keywords[-1] == "}": - inside_object_container_class = False - container_classes[current_class_name] = (current_class_docs) - current_class_name = None - continue - - if inside_abstract_class and keywords[-1] == "}": - inside_abstract_class = False - continue - - if inside_object_class and keywords[-1] == "}": - inside_object_class = False - object_classes[current_class_name] = (current_constructor, container_class_name, - current_arguments, current_class_docs) - container_class_name = None - current_arguments = None - current_class_name = None - current_constructor = None - continue - - if inside_function_class and keywords[0] == "}": - inside_function_class = False - function_classes[current_class_name] = (current_constructor, container_class_name, - current_arguments, current_class_docs) - current_arguments = None - container_class_name = None - current_class_name = None - current_constructor = None - continue - - if inside_function_class or inside_object_class: - if len(keywords) == 3 and keywords[-1].endswith(";"): - current_arguments.append((keywords[1], keywords[2][:-1], extract_doc(lines, no))) - continue - - if len(keywords) == 7 and keywords[4] == "CONSTRUCTOR": - current_constructor = int(keywords[6][:-1]) - continue - - if keywords[0] == "package": - package = line - continue - - if len(keywords) == 8 and keywords[1] == "abstract": - inside_object_container_class = True - current_class_name = keywords[4] - current_class_docs = extract_doc(lines, no) - continue - - if len(keywords) == 4 and keywords[2] == "TdApi": - continue - - if len(keywords) == 6 and keywords[1] == "abstract": - inside_abstract_class = True - continue - - if len(keywords) == 7 and keywords[2] == "class" and keywords[5] == "Object": - current_class_name = keywords[-4] - current_arguments = [] - inside_object_class = True - current_class_docs = extract_doc(lines, no) - container_class_name = keywords[5] - continue - - if len(keywords) == 7 and keywords[2] == "class" and keywords[5] == "Function": - current_class_name = keywords[-4] - current_arguments = [] - inside_function_class = True - current_class_docs = extract_doc(lines, no) - container_class_name = keywords[5] - continue - - if len(keywords) == 7 and keywords[2] == "class" and keywords[5] in container_classes: - current_class_name = keywords[-4] - current_arguments = [] - inside_object_class = True - current_class_docs = extract_doc(lines, no) - container_class_name = keywords[5] - continue - - data_input.close() - - data_output = open(output_path, "w") - data_output.write(package + "\n\n") - data_output.write(open(headers_path).read()) - del container_classes["Function"] - - output = CodeWriter(data_output, 1) - - output.indent() - output.open_custom_block(f"public static class Deserializer") - output.newline() - - output.indent() - output.open_function("deserialize", [("DataInput", "input")], "static Object", "IOException") - output.newline() - - output.indent() - output.open_switch("input.readInt()") - output.newline() - - for classes in (object_classes, function_classes): - for class_name in classes.keys(): - output.indent() - output.open_switch_case(f"{class_name}.CONSTRUCTOR") - output.newline() - - output.indent() - output.ret(f"new {class_name}(input)") - output.newline() - - output.indent_depth -= 1 - - output.indent() - output.open_switch_default() - output.newline() - - output.indent() - output.exception("UnsupportedOperationException") - output.newline() - output.indent_depth -= 1 - - output.close_block(space=True) - output.close_block(space=True) - output.close_block(space=True) - - output.newline() - - for container_class_name, container_class_meta in container_classes.items(): - output.indent() - output.open_docs() - - for docs in [container_class_meta[0]]: - for doc in split_docs([docs]): - output.newline() - output.indent() - output.write_docs(doc) - - output.newline() - output.indent() - output.close_docs() - - output.newline() - output.indent() - - allowed_classess = [] - - for classes in (object_classes, function_classes): - for class_name, class_meta in classes.items(): - if container_class_name == class_meta[1]: - allowed_classess.append(class_name) - - allowed_classess_str = "permits " - - for i, allowed_classess_group in enumerate(chunker(allowed_classess, 3)): - if i: - allowed_classess_str += ",\n" - allowed_classess_str += output.indent_chr * (output.indent_depth + 1) - allowed_classess_str += ", ".join(allowed_classess_group) - - if allowed_classess and java17: - output.open_custom_block(f"public abstract static sealed class", container_class_name, "extends Object", allowed_classess_str) - else: - output.open_custom_block(f"public abstract static class", container_class_name, "extends Object") - - output.close_block() - output.newline() - - for classes in (object_classes, function_classes): - for class_name, class_meta in classes.items(): - output.indent() - output.open_docs() - - for docs in class_meta[3]: - for doc in split_docs([docs]): - output.newline() - output.indent() - output.write_docs(doc) - - output.newline() - output.indent() - output.close_docs() - - output.newline() - output.indent() - - class_generics = "" - - if class_meta[1] == "Function": - class_generics = "<" - class_generics += docs.split("@link ")[1].split()[0] - class_generics += ">" - - output.open_custom_block("public static final class", class_name, "extends", class_meta[1] + class_generics) - - for arg_type, arg_name, docs in class_meta[2]: - output.newline() - output.indent() - output.open_docs() - output.newline() - output.indent() - for doc in split_docs(docs): - output.write_docs(doc) - output.newline() - output.indent() - output.close_docs() - output.newline() - output.indent() - - output.declare(arg_name, arg_type, "public") - output.newline() - - output.newline() - output.indent() - output.declare("CONSTRUCTOR", "int", "public static final", value=str(class_meta[0])) - output.newline() - output.newline() - - output.indent() - output.open_docs() - - for docs in class_meta[3]: - for doc in split_docs([docs]): - output.newline() - output.indent() - output.write_docs(doc) - - output.newline() - output.indent() - output.close_docs() - - output.newline() - output.indent() - output.open_constructor_function(class_name, []) - output.close_block() - output.newline() - - output.indent() - output.open_docs() - - for docs in class_meta[3]: - for doc in split_docs([docs]): - output.newline() - output.indent() - output.write_docs(doc) - - for arg_name, arg_type, docs in class_meta[2]: - output.newline() - output.indent() - output.write_docs() - docs = split_docs(docs) - output.newline() - output.indent() - output.write_docs(f"@param {arg_type} {arg_name} {docs[0]}") - - for doc in docs[1:]: - output.newline() - output.indent() - output.write_docs() - output.fd.write(f" {doc}") - - output.newline() - output.indent() - output.close_docs() - - output.newline() - - if class_meta[2]: - output.indent() - output.open_constructor_function(class_name, [(x[0], x[1]) for x in class_meta[2]]) - output.newline() - - for arg_type, arg_name, _ in class_meta[2]: - output.indent() - output.class_assign(arg_name, arg_name) - output.newline() - - output.close_block(space=True) - output.newline() - - output.indent() - output.open_docs() - - for docs in class_meta[3]: - for doc in split_docs([docs]): - output.newline() - output.indent() - output.write_docs(doc) - - output.newline() - output.indent() - output.close_docs() - output.newline() - - output.indent() - output.open_constructor_function(class_name, [("DataInput", "input")], "IOException") - output.newline() - - for arg_type, arg_name, _ in class_meta[2]: - if arg_type in natives: - deserialize_native(output, arg_name, arg_type) - - elif not arg_type.endswith("[]"): - deserialize_tdapi(output, arg_name, arg_type, container_classes, object_classes) - - elif arg_type == "byte[][]" or not arg_type.endswith("[][]"): - output.indent() - output.open_if("input.readBoolean()") - output.newline() - - output.indent() - if arg_type == "byte[][]": - output.class_assign(arg_name, f"new byte[input.readInt()][]") - else: - output.class_assign(arg_name, f"new {arg_type[:-2]}[input.readInt()]") - output.newline() - - output.indent() - output.open_for("int i = 0", f"i < this.{arg_name}.length", "i++") - output.newline() - - if arg_type[:-2] in natives: - deserialize_native(output, f"{arg_name}[i]", arg_type[:-2], null_check=False) - - else: - deserialize_tdapi(output, f"{arg_name}[i]", arg_type[:-2], - container_classes, object_classes, null_check=False) - - output.close_block(space=True) - output.close_block(space=True) - - elif arg_type.endswith("[][]"): - output.indent() - output.open_if("input.readBoolean()") - output.newline() - - output.indent() - output.class_assign(arg_name, f"new {arg_type[:-4]}[input.readInt()][]") - output.newline() - - output.indent() - output.open_for("int i = 0", f"i < this.{arg_name}.length", "i++") - output.newline() - - output.indent() - output.class_assign(f"{arg_name}[i]", f"new {arg_type[:-4]}[input.readInt()]") - output.newline() - - output.indent() - output.open_for("int j = 0", f"j < this.{arg_name}[i].length", "j++") - output.newline() - - if arg_type[:-4] in natives: - deserialize_native(output, f"{arg_name}[i][j]", arg_type[:-4], null_check=False) - - else: - deserialize_tdapi(output, f"{arg_name}[i][j]", arg_type[:-4], - container_classes, object_classes, null_check=False) - - output.close_block(space=True) - output.close_block(space=True) - output.close_block(space=True) - - output.close_block(space=True) - - output.newline() - output.indent() - output.open_function("getConstructor", [], "int") - output.newline() - - output.indent() - output.ret("CONSTRUCTOR") - output.newline() - - output.close_block(space=True) - - output.newline() - output.indent() - output.open_function("serialize", [("DataOutput", "output")], "void", "IOException") - output.newline() - - output.indent() - output.call("output.writeInt", f"CONSTRUCTOR") - output.newline() - - for arg_type, arg_name, _ in class_meta[2]: - if arg_type in natives: - serialize_native(output, arg_type, arg_name) - - elif not arg_type.endswith("[]"): - serialize_tdapi(output, arg_name) - - elif arg_type == "byte[][]" or not arg_type.endswith("[][]"): - output.indent() - output.open_if(f"this.{arg_name} == null") - output.newline() - - output.indent() - output.call("output.writeBoolean", "false") - output.newline() - - output.open_if_else(space=True) - output.newline() - - output.indent() - output.call("output.writeBoolean", "true") - output.newline() - - output.indent() - output.call("output.writeInt", f"this.{arg_name}.length") - output.newline() - - output.indent() - output.open_for("int i = 0", f"i < this.{arg_name}.length", "i++") - output.newline() - - if arg_type[:-2] in natives: - serialize_native(output, arg_type[:-2], f"{arg_name}[i]", null_check=False) - else: - serialize_tdapi(output, f"{arg_name}[i]", null_check=False) - - output.close_block(space=True) - output.close_block(space=True) - - elif arg_type.endswith("[][]"): - output.indent() - output.open_if(f"this.{arg_name} == null") - output.newline() - - output.indent() - output.call("output.writeBoolean", "false") - output.newline() - - output.open_if_else(space=True) - output.newline() - - output.indent() - output.call("output.writeBoolean", "true") - output.newline() - - output.indent() - output.call("output.writeInt", f"this.{arg_name}.length") - output.newline() - - output.indent() - output.open_for("int i = 0", f"i < this.{arg_name}.length", "i++") - output.newline() - - output.indent() - output.call("output.writeInt", f"this.{arg_name}[i].length") - output.newline() - - output.indent() - output.open_for("int j = 0", f"j < this.{arg_name}[i].length", "j++") - output.newline() - - if arg_type[:-4] in natives: - serialize_native(output, arg_type[:-4], f"{arg_name}[i][j]", null_check=False) - else: - serialize_tdapi(output, f"{arg_name}[i][j]", null_check=False) - - output.close_block(space=True) - output.close_block(space=True) - output.close_block(space=True) - - output.close_block(space=True) - - output.newline() - output.indent() - output.open_function("equals", [("java.lang.Object", "o")], "boolean") - output.newline() - - output.indent() - output.open_if("this == o") - output.newline() - output.indent() - output.ret("true") - output.newline() - output.close_block(space=True) - - output.indent() - output.open_if("o == null || getClass() != o.getClass()") - output.newline() - output.indent() - output.ret("false") - output.newline() - output.close_block(space=True) - - if class_meta[2]: - output.indent() - other_class = class_name[0].lower() + class_name[1:] - output.local_assign(class_name, other_class, f"({class_name}) o") - - output.newline() - - for arg_type, arg_name, _ in class_meta[2]: - output.indent() - - if arg_type in cmp_natives: - output.open_if(f"this.{arg_name} != {other_class}.{arg_name}") - - elif not arg_type.endswith("[]"): - output.open_if(f"!Objects.equals(this.{arg_name}, {other_class}.{arg_name})") - - elif arg_type.endswith("[][]"): - output.open_if(f"!Arrays.deepEquals(this.{arg_name}, {other_class}.{arg_name})") - - else: - output.open_if(f"!Arrays.equals(this.{arg_name}, {other_class}.{arg_name})") - - output.newline() - output.indent() - output.ret("false") - output.newline() - output.close_block(space=True) - - output.indent() - output.ret("true") - output.newline() - - output.close_block(space=True) - - output.newline() - output.indent() - output.open_function("hashCode", [], "int") - output.newline() - - output.indent() - - if class_meta[2]: - primitives = [(t, n) for t, n, _ in class_meta[2] if t in cmp_natives] - - if primitives and len(class_meta[2]) == 1: - output.ret(f"{native_to_object[primitives[0][0]]}.hashCode(this.{primitives[0][1]})") - - elif primitives: - output.local_assign("int", "result", - f"{native_to_object[primitives[0][0]]}.hashCode(this.{primitives[0][1]})") - output.newline() - output.indent() - - for arg_type, arg_name in primitives[1:]: - output.assign("result", f"result * 31 + " - f"{native_to_object[arg_type]}.hashCode(this.{arg_name})") - output.newline() - output.indent() - - tdapi = [(t, n) for t, n, _ in class_meta[2] if n not in [p[1] for p in primitives]] - - if tdapi and len(class_meta[2]) == 1: - output.ret(hash_object(f"this.{tdapi[0][1]}", tdapi[0][0])) - start = 1 - - else: - if not primitives: - output.local_assign("int", "result", hash_object(f'this.{tdapi[0][1]}', tdapi[0][0])) - output.newline() - output.indent() - start = 1 - else: - start = 0 - - for arg_type, arg_name in tdapi[start:]: - output.assign("result", f"result * 31 + ({hash_object(f'this.{arg_name}', arg_type)})") - output.newline() - output.indent() - - if len(class_meta[2]) > 1: - output.ret("result") - - else: - output.ret("CONSTRUCTOR") - - output.newline() - - output.close_block(space=True) - output.close_block(space=True) - output.newline() - - data_output.seek(data_output.tell() - 1) - data_output.write("}") - - -if __name__ == '__main__': - main(sys.argv[-4], sys.argv[-3], sys.argv[-2], sys.argv[-1]) diff --git a/code_writer.py b/code_writer.py deleted file mode 100644 index a2aeaea..0000000 --- a/code_writer.py +++ /dev/null @@ -1,151 +0,0 @@ -import typing - - -indent = "\t" - - -class CodeWriter: - fd: typing.TextIO - indent_depth: int - indent_chr = indent - - def split_args(self, args: str) -> typing.List[str]: - tokens = args.split(", ") - result = [tokens[0]] - - for token in tokens[1:]: - if len(", ".join(result[-1]) + token) < 120: - result[-1] += f", {token}" - else: - result.append(token) - - if len(result) > 1 and len(result[-1].split(", ")) < 3: - result[-2] += f", {result[-1]}" - del result[-1] - - return result - - def __init__(self, file: typing.TextIO, indent_depth: int = 0): - self.fd = file - self.indent_depth = indent_depth - - def indent(self): - self.fd.write(indent * self.indent_depth) - - def open_custom_block(self, *blocks: str): - self.fd.write(" ".join(blocks) + " {") - self.indent_depth += 1 - - def open_docs(self): - self.fd.write("/**") - - def write_docs(self, text: str = ""): - if text.strip() != "": - self.fd.write(f" * {text}") - else: - self.fd.write(" *") - - def close_docs(self): - self.fd.write(" */") - - def class_assign(self, class_value, local_value): - self.fd.write("this." + class_value + " = " + local_value + ";") - - def close_block(self, space: bool = False, newline: bool = True): - self.indent_depth -= 1 - if space: - self.indent() - self.fd.write("}\n" if newline else "}") - - def newline(self): - self.fd.write("\n") - - def open_switch(self, data: str): - self.indent_depth += 1 - self.fd.write("switch(" + data + ") {") - - def switch_break(self): - self.indent_depth -= 1 - self.fd.write("break;") - - def open_switch_case(self, case: str): - self.indent_depth += 1 - self.fd.write("case " + case + ":") - - def open_switch_default(self): - self.indent_depth += 1 - self.fd.write("default:") - - def exception(self, exception_class: str): - self.fd.write("throw new " + exception_class + "();") - - def open_if_else(self, space: bool = False): - self.indent_depth -= 1 - if space: - self.indent() - self.fd.write("} else {") - self.indent_depth += 1 - - def local_assign(self, object_type: str, name: str, value: str): - self.fd.write(object_type + " " + name + " = " + value + ";") - - def assign(self, name: str, value: str): - self.fd.write(name + " = " + value + ";") - - def open_for(self, start: str, cond: str, stmt: str): - self.indent_depth += 1 - self.fd.write("for (" + start + "; " + cond + "; " + stmt + ") {") - - def open_if(self, cond: str): - self.indent_depth += 1 - self.fd.write("if (" + cond + ") {") - - def call(self, method: str, *args: str): - self.fd.write(method + "(" + ", ".join(args) + ");") - - def ret(self, var: str): - self.fd.write("return " + var + ";") - - def open_function(self, name: str, args: typing.List[typing.Tuple[str, str]], t: str, e: str = None): - self.indent_depth += 1 - result = "public " + t + " " + name + "(" + ", ".join((t + " " + n for t, n in args)) + ")" - if e: - result += " throws " + e - result += " {" - self.fd.write(result) - - def open_constructor_function(self, name: str, args: typing.List[typing.Tuple[str, str]], e: str = None): - self.indent_depth += 1 - result = "public " + name + "(" - - result_args = ", ".join((t + " " + n for t, n in args)) - - if len(result_args) > 60: - result_args = result_args.split(", ") - - for i, result_arg in enumerate(result_args): - if i: - result += (self.indent_depth + 1) * indent - - result += result_arg - - if i != len(result_args) - 1: - result += ",\n" - else: - result += result_args - - result += ")" - - if e: - result += " throws " + e - - result += " {" - - self.fd.write(result) - - def declare(self, name: str, typ: str, flags: str, value: str = None): - result = flags + " " + typ + " " + name - if value: - result += " = " + value - result += ";" - self.fd.write(result) diff --git a/generate.sh b/generate.sh deleted file mode 100644 index f57f91a..0000000 --- a/generate.sh +++ /dev/null @@ -1,2 +0,0 @@ -wget https://git.ignuranza.net/andreacavalli/JTDLib/raw/branch/master/src/main/java/it/ernytech/tdlib/TdApi.java -O TdApi.java -python3 . TdApi.java headers.txt diff --git a/transform.java b/transform.java new file mode 100644 index 0000000..6612bc9 --- /dev/null +++ b/transform.java @@ -0,0 +1,1000 @@ +import java.io.Closeable; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class transform { + + static List NATIVES = List.of("int", "long", "boolean", "String", "double", "byte[]", "byte"); + static Map NATIVE_TO_OBJ = Map.of("boolean", "Boolean", + "byte[]", "Byte", + "byte", "Byte", + "int", "Integer", + "short", "Short", + "char", "Character", + "long", "Long", + "float", "Float", + "double", "Double", + "String", "String"); + + public static void main(String[] args) throws IOException, URISyntaxException { + var headersPath = Path.of(transform.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent().resolve("headers.txt"); + + // Parse arguments + Path sourcePath = null; + Path outputPath = null; + boolean tempJava17 = false; + boolean tempOverwrite = false; + for (int i = 0; i < args.length; i++) { + if (i + 1 >= args.length) throw getHelp(); + switch (args[i]) { + case "--source": { + sourcePath = Path.of(args[++i]); + break; + } + case "--output": { + outputPath = Path.of(args[++i]); + break; + } + case "--java17": { + tempJava17 = Boolean.parseBoolean(args[++i]); + break; + } + case "--overwrite": { + tempOverwrite = Boolean.parseBoolean(args[++i]); + break; + } + case "--headers": { + headersPath = Path.of(args[++i]); + if (!Files.isRegularFile(headersPath)) { + System.err.println("The headers file is not a regular file: " + headersPath); + System.exit(1); + } + if (!Files.isReadable(headersPath)) { + System.err.println("The headers file is not a readable file: " + headersPath); + System.exit(1); + } + break; + } + default: throw getHelp(); + } + } + // Check required arguments + if (sourcePath == null || outputPath == null) throw getHelp(); + boolean java17 = tempJava17; + boolean overwrite = tempOverwrite; + var headers = Files.readString(headersPath, StandardCharsets.UTF_8); + + String package_ = null; + Map functionClasses = new LinkedHashMap<>(); + Map objectClasses = new LinkedHashMap<>(); + Map> containerClassesDocs = new LinkedHashMap<>(); + List currentArguments = new ArrayList<>(); + + // Parse input document + { + int currentConstructor = -1; + String currentClassName = null; + boolean insideAbstractClass = false; + boolean insideObjectClass = false; + boolean insideFunctionClass = false; + boolean insideObjectContainerClass = false; + String containerClassName = null; + List currentClassDocs = new ArrayList<>(); + int functionDepth = 0; + + // Read the input document + List lines; + try (var l = Files.lines(sourcePath)) { + lines = l.map(String::strip).map(x -> !x.strip().startsWith("*") ? x.replaceAll("<[a-zA-Z_]*>", "") : x).collect(Collectors.toList()); + } + + int no = 0; + for (String line : lines) { + var keywords = Arrays.stream(line.split("[ \t]")).filter(l -> !l.isBlank()).collect(Collectors.toList()); + + lineProcess: { + if (keywords.isEmpty()) { + break lineProcess; + } + + var firstKeyword = keywords.get(0); + var lastKeyword = keywords.get(keywords.size() - 1); + + boolean startsWithOpenParenthesis = lastKeyword.equals("{"); + boolean startsWithClosedParenthesis = lastKeyword.equals("}"); + boolean endsWithOpenParenthesis = lastKeyword.equals("{"); + boolean endsWithClosedParenthesis = lastKeyword.equals("}"); + + if ((insideObjectClass || insideFunctionClass) && endsWithOpenParenthesis) { + functionDepth++; + break lineProcess; + } + + if ((insideObjectClass || insideFunctionClass) && endsWithClosedParenthesis && functionDepth > 0) { + functionDepth--; + break lineProcess; + } + + if (functionDepth > 0) { + break lineProcess; + } + + if (insideObjectContainerClass && endsWithClosedParenthesis) { + insideObjectContainerClass = false; + containerClassesDocs.put(currentClassName, copyNullableList(currentClassDocs)); + currentClassName = null; + break lineProcess; + } + + if (insideAbstractClass && endsWithClosedParenthesis) { + insideAbstractClass = false; + break lineProcess; + } + + if (insideObjectClass && endsWithClosedParenthesis) { + insideObjectClass = false; + var type = new TdType(); + type.constructorId = currentConstructor; + type.containerName = containerClassName; + type.arguments = new ArrayList<>(currentArguments); + type.docs = copyNullableList(currentClassDocs); + objectClasses.put(currentClassName, type); + currentClassName = null; + break lineProcess; + } + + if (insideAbstractClass && endsWithClosedParenthesis) { + insideAbstractClass = false; + break lineProcess; + } + + if (insideObjectClass && endsWithClosedParenthesis) { + insideObjectClass = false; + var type = new TdType(); + type.constructorId = currentConstructor; + type.containerName = containerClassName; + type.arguments = new ArrayList<>(currentArguments); + type.docs = copyNullableList(currentClassDocs); + objectClasses.put(currentClassName, type); + containerClassName = null; + currentArguments = new ArrayList<>(); + currentClassName = null; + currentConstructor = -1; + } + + if (insideFunctionClass && startsWithClosedParenthesis) { + insideFunctionClass = false; + var type = new TdType(); + type.constructorId = currentConstructor; + type.containerName = containerClassName; + type.arguments = new ArrayList<>(currentArguments); + type.docs = copyNullableList(currentClassDocs); + functionClasses.put(currentClassName, type); + currentArguments = new ArrayList<>(); + containerClassName = null; + currentClassName = null; + currentConstructor = -1; + } + + if (insideFunctionClass || insideObjectClass) { + if (keywords.size() == 3 && lastKeyword.endsWith(";")) { + var arg = new CurrentArgument(); + arg.type = keywords.get(1); + arg.name = removeLastChars(keywords.get(2), 1); + arg.docs = extractDoc(lines, no); + currentArguments.add(arg); + break lineProcess; + } + if (keywords.size() == 7 && keywords.get(4).equals("CONSTRUCTOR")) { + currentConstructor = Integer.parseInt(removeLastChars(keywords.get(6), 1)); + break lineProcess; + } + } + + if (firstKeyword.equals("package")) { + package_ = line; + break lineProcess; + } + + if (keywords.size() == 8 && keywords.get(1).equals("abstract")) { + insideObjectContainerClass = true; + currentClassName = keywords.get(4); + currentClassDocs = extractDoc(lines, no); + break lineProcess; + } + + if (keywords.size() == 4 && keywords.get(2).equals("TdApi")) { + break lineProcess; + } + + if (keywords.size() == 6 && keywords.get(1).equals("abstract")) { + insideAbstractClass = true; + break lineProcess; + } + + if (keywords.size() == 7 && keywords.get(2).equals("class") && keywords.get(5).equals("Object")) { + currentClassName = keywords.get(keywords.size() - 4); + currentArguments = new ArrayList<>(); + insideObjectClass = true; + currentClassDocs = extractDoc(lines, no); + containerClassName = keywords.get(5); + break lineProcess; + } + + if (keywords.size() == 7 && keywords.get(2).equals("class") && keywords.get(5).equals("Function")) { + currentClassName = keywords.get(keywords.size() - 4); + currentArguments = new ArrayList<>(); + insideFunctionClass = true; + currentClassDocs = extractDoc(lines, no); + containerClassName = keywords.get(5); + break lineProcess; + } + + if (keywords.size() == 7 && keywords.get(2).equals("class") && containerClassesDocs.containsKey(keywords.get(5))) { + currentClassName = keywords.get(keywords.size() - 4); + currentArguments = new ArrayList<>(); + insideObjectClass = true; + currentClassDocs = extractDoc(lines, no); + containerClassName = keywords.get(5); + break lineProcess; + } + } + + no++; + } + + containerClassesDocs.remove("Function"); + } + Map allClasses = new LinkedHashMap<>(); + allClasses.putAll(objectClasses); + allClasses.putAll(functionClasses); + + // Write output document + { + List openFlags = new ArrayList<>(); + openFlags.add(StandardOpenOption.WRITE); + if (overwrite) { + openFlags.add(StandardOpenOption.CREATE); + openFlags.add(StandardOpenOption.TRUNCATE_EXISTING); + } else { + openFlags.add(StandardOpenOption.CREATE_NEW); + } + try (var w = new JavaWriter(Files.newBufferedWriter(outputPath, StandardCharsets.UTF_8, openFlags.toArray(OpenOption[]::new)), 1)) { + // Write package + w.write(package_).writeNewLine().writeNewLine(); + // Write header + w.write(headers); + + w.writeIndent().writeOpenCustomBlock("public static class Deserializer").writeNewLine(); + + w.writeIndent().writeOpenFunction("deserialize", List.of(Map.entry("DataInput", "input")), "static Object", "IOException").writeNewLine(); + + w.writeIndent().writeOpenSwitch("input.readInt()").writeNewLine(); + + allClasses.forEach((className, x) -> { + w.writeIndent().writeOpenSwitch(className + ".CONSTRUCTOR").writeNewLine(); + w.writeIndent().writeReturn("new " + className + "(input)").writeNewLine(); + + w.decrementIndentation(); + }); + + w.writeIndent().writeOpenSwitchDefault().writeNewLine(); + + w.writeIndent().writeException("UnsupportedOperationException").writeNewLine(); + w.decrementIndentation(); + + w.writeCloseBlock(true).writeNewLine(); + w.writeCloseBlock(true).writeNewLine(); + w.writeCloseBlock(true).writeNewLine(); + + w.writeNewLine(); + + containerClassesDocs.forEach((containerClassName, containerClassMeta) -> { + w.writeIndent().writeOpenDocs(); + var docs = containerClassMeta; + splitDocs(docs).forEach(docLine -> w.writeNewLine().writeIndent().writeDocs(docLine)); + w.writeNewLine().writeIndent().writeCloseDocs(); + w.writeNewLine().writeIndent(); + + var allowedClasses = new LinkedHashSet(); + + allClasses.forEach((className, classMeta) -> { + if (containerClassName.equals(classMeta.containerName)) { + allowedClasses.add(className); + } + }); + + if (java17) { + w.write("public abstract static sealed class " + containerClassName + " extends Object permits "); + w.incrementIndentation(); + var lines = streamGrouped(allowedClasses.stream(), 3) + .map(permitGroup -> String.join(", ", permitGroup)) + .collect(Collectors.toList()); + for (int i = 0; i < lines.size(); i++) { + if (i > 0) { + w.write(",").writeNewLine().writeIndent(); + } + w.write(lines.get(i)); + } + w.decrementIndentation(); + w.writeOpenCustomBlock(""); + } else { + w.writeOpenCustomBlock("public abstract static class " + containerClassName + " extends Object"); + } + + w.writeCloseBlock().writeNewLine().writeNewLine(); + }); + + allClasses.forEach((className, classMeta) -> { + w.writeIndent().writeOpenDocs(); + splitDocs(classMeta.docs).forEach(doc -> w.writeNewLine().writeIndent().writeDocs(doc)); + w.writeNewLine().writeIndent().writeCloseDocs(); + w.writeNewLine().writeIndent(); + String classGenerics; + if (classMeta.containerName.equals("Function")) { + classGenerics = "<" + extractGeneric(classMeta.docs) + ">"; + } else { + classGenerics = ""; + } + w.writeOpenCustomBlock("public static final class", className, "extends", classMeta.containerName + classGenerics); + + classMeta.arguments.forEach(argument -> { + w.writeNewLine().writeIndent().writeOpenDocs().writeNewLine().writeIndent(); + argument.docs.forEach(doc -> w.writeDocs(doc).writeNewLine().writeIndent()); + w.writeCloseDocs().writeNewLine().writeIndent(); + w.writeDeclare(argument.name, argument.type, "public", null); + w.writeNewLine(); + }); + + w.writeNewLine(); + w.writeIndent().writeOpenDocs().writeNewLine().writeIndent().writeDocs("Identifier uniquely determining type of the object.").writeNewLine().writeIndent().writeCloseDocs().writeNewLine(); + w.writeIndent().writeDeclare("CONSTRUCTOR", "int", "public static final", String.valueOf(classMeta.constructorId)); + w.writeNewLine().writeNewLine().writeIndent().writeOpenDocs(); + classMeta.docs.forEach(doc -> w.writeNewLine().writeIndent().writeDocs(doc)); + w.writeNewLine().writeIndent().writeCloseDocs(); + + w.writeNewLine().writeIndent(); + w.writeOpenConstructorFunction(className, List.of(), null); + w.writeCloseBlock().writeNewLine(); + + w.writeIndent().writeOpenDocs(); + + classMeta.docs.forEach(doc -> w.writeNewLine().writeIndent().writeDocs(doc)); + + w.writeNewLine().writeIndent().writeDocs(""); + classMeta.arguments.forEach(arg -> { + var docs = arg.docs; + w.writeNewLine().writeIndent(); + w.writeDocs("@param " + arg.type + " " + arg.name + " " + docs.get(0)); + }); + classMeta.docs.subList(1, classMeta.docs.size()).forEach(doc -> w.writeNewLine().writeIndent().writeDocs(doc)); + w.writeNewLine().writeIndent().writeCloseDocs(); + w.writeNewLine(); + if (!classMeta.arguments.isEmpty()) { + w.writeIndent().writeOpenConstructorFunction(className, classMeta.arguments.stream().map(a -> Map.entry(a.name, a.type)).collect(Collectors.toList()), null); + w.writeNewLine(); + classMeta.arguments.forEach(arg -> w.writeIndent().writeClassAssign(arg.name, arg.name).writeNewLine()); + w.writeCloseBlock(true).writeNewLine(); + + w.writeNewLine().writeIndent().writeOpenDocs(); + classMeta.docs.forEach(doc -> w.writeNewLine().writeIndent().writeDocs(doc)); + w.writeNewLine().writeIndent().writeCloseDocs().writeNewLine(); + } + w.writeIndent().writeOpenConstructorFunction(className, List.of(Map.entry("DataInput", "input")), "IOException").writeNewLine(); + + classMeta.arguments.forEach(arg -> { + if (NATIVES.contains(arg.type)) { + deserializeNative(w, arg.name, arg.type); + } else if (!arg.type.endsWith("[]")) { + deserializeTdApi(w, arg.name, arg.type, containerClassesDocs, objectClasses); + } else if (arg.type.equals("byte[][]") || !arg.type.endsWith("[][]")) { + var baseArgType = arg.type.substring(0, arg.type.length() - 2); + w.writeIndent().writeOpenIf("input.readBoolean()").writeNewLine(); + w.writeIndent(); + if (arg.type.equals("byte[][]")) { + w.writeClassAssign(arg.name, "new byte[input.readInt()][]"); + } else { + w.writeClassAssign(arg.name, "new " + baseArgType + "[input.readInt()]"); + } + w.writeNewLine(); + w.writeIndent().writeOpenFor("int i = 0", "i < this." + arg.name + ".length", "i++").writeNewLine(); + + if (NATIVES.contains(baseArgType)) { + deserializeNative(w, arg.name + "[i]", baseArgType, false); + } else { + deserializeTdApi(w, arg.name + "[i]", baseArgType, containerClassesDocs, objectClasses, false); + } + w.writeCloseBlock(true).writeNewLine(); + w.writeCloseBlock(true).writeNewLine(); + } else if (arg.type.endsWith("[][]")) { + var baseArgType = arg.type.substring(0, arg.type.length() - 4); + w.writeIndent().writeOpenIf("input.readBoolean()").writeNewLine(); + w.writeIndent().writeClassAssign(arg.name, "new " + baseArgType + "[input.readInt()][]").writeNewLine(); + w.writeIndent().writeOpenFor("int i = 0", "i < this." + arg.name + ".length", "i++").writeNewLine(); + w.writeIndent().writeClassAssign(arg.name + "[i]", "new " + baseArgType + "[input.readInt()]"); + w.writeIndent().writeOpenFor("int j = 0", "j < this."+arg.name+"[i].length", "j++").writeNewLine(); + if (NATIVES.contains(baseArgType)) { + deserializeNative(w, arg.name + "[i][j]", baseArgType, false); + } else { + deserializeTdApi(w, arg.name + "[i][j]", baseArgType, containerClassesDocs, objectClasses, false); + } + w.writeCloseBlock(true).writeNewLine(); + w.writeCloseBlock(true).writeNewLine(); + w.writeCloseBlock(true).writeNewLine(); + } + }); + w.writeCloseBlock(true).writeNewLine(); + w.writeNewLine().writeIndent().writeOpenDocs().writeNewLine(); + w.writeIndent().writeDocs("@return this.CONSTRUCTOR").writeNewLine(); + w.writeIndent().writeCloseDocs().writeNewLine(); + w.writeIndent().writeOpenFunction("getConstructor", List.of(), "int", null).writeNewLine(); + w.writeIndent().writeReturn("CONSTRUCTOR").writeNewLine(); + w.writeCloseBlock(true).writeNewLine(); + w.writeNewLine(); + w.writeIndent().writeOpenFunction("serialize", List.of(Map.entry("DataOutput", "output")), "void", "IOException").writeNewLine(); + w.writeIndent().writeCall("output.writeInt", "CONSTRUCTOR").writeNewLine(); + classMeta.arguments.forEach(arg -> { + if (NATIVES.contains(arg.type)) { + serializeNative(w, arg.name, arg.type); + } else if (!arg.type.endsWith("[]")) { + serializeTdApi(w, arg.name); + } else if (arg.type.equals("byte[][]") || !arg.type.endsWith("[][]")) { + var baseArgType = arg.type.substring(0, arg.type.length() - 2); + w.writeIndent().writeOpenIf("this." + arg.name + " == null").writeNewLine(); + w.writeIndent().writeCall("output.writeBoolean", "false").writeNewLine(); + w.writeOpenIfElse(true).writeNewLine(); + w.writeIndent().writeCall("output.writeBoolean", "true").writeNewLine(); + w.writeIndent().writeCall("output.writeInt", "this." + arg.name + ".length").writeNewLine(); + w.writeIndent().writeOpenFor("int i = 0", "i < this." + arg.name + ".length", "i++").writeNewLine(); + if (NATIVES.contains(baseArgType)) { + serializeNative(w, baseArgType, arg.name + "[i]", false); + } else { + serializeTdApi(w, arg.name + "[i]", false); + } + w.writeCloseBlock(true).writeNewLine(); + w.writeCloseBlock(true).writeNewLine(); + } else if (arg.type.endsWith("[][]")) { + var baseArgType = arg.type.substring(0, arg.type.length() - 4); + w.writeIndent().writeOpenIf("this." + arg.name + " == null").writeNewLine(); + w.writeIndent().writeCall("output.writeBoolean", "false").writeNewLine(); + w.writeOpenIfElse(true).writeNewLine(); + w.writeIndent().writeCall("output.writeBoolean", "true").writeNewLine(); + w.writeIndent().writeCall("output.writeInt", "this." + arg.name + ".length").writeNewLine(); + w.writeIndent().writeOpenFor("int i = 0", "i < this." + arg.name + ".length", "i++").writeNewLine(); + w.writeIndent().writeCall("output.writeInt", "this." + arg.name + "[i].length").writeNewLine(); + w.writeIndent().writeOpenFor("int j = 0", "j < this." + arg.name + "[i].length", "j++").writeNewLine(); + if (NATIVES.contains(baseArgType)) { + serializeNative(w, baseArgType, arg.name + "[i][j]", false); + } else { + serializeTdApi(w, arg.name + "[i][j]", false); + } + w.writeCloseBlock(true).writeNewLine(); + w.writeCloseBlock(true).writeNewLine(); + w.writeCloseBlock(true).writeNewLine(); + } + }); + w.writeCloseBlock(true).writeNewLine(); + + w.writeNewLine(); + w.writeNewLine().writeIndent().writeOpenFunction("equals", List.of(Map.entry("java.lang.Object", "o")), "boolean", null).writeNewLine(); + w.writeIndent().writeOpenIf("this == o").writeNewLine(); + w.writeIndent().writeReturn("true").writeNewLine().writeCloseBlock(true).writeNewLine(); + w.writeIndent().writeOpenIf("o == null || getClass() != o.getClass()").writeNewLine(); + w.writeIndent().writeReturn("false").writeNewLine(); + w.writeCloseBlock(true).writeNewLine(); + if (!classMeta.arguments.isEmpty()) { + w.writeIndent(); + var otherClass = className.substring(0, 1).toLowerCase(Locale.US) + className.substring(1); + w.writeLocalAssign(className, otherClass, "(" + className + ") o"); + w.writeNewLine(); + classMeta.arguments.forEach(arg -> { + w.writeIndent(); + if (NATIVE_TO_OBJ.containsKey(arg.type)) { + w.writeOpenIf("this." + arg.name + " != " + otherClass + "." + arg.name); + } else if (!arg.type.endsWith("[]")) { + w.writeOpenIf("!Objects.equals(this." + arg.name + ", " + otherClass + "." + arg.name + ")"); + } else if (arg.type.endsWith("[][]")) { + w.writeOpenIf("!Arrays.deepEquals(this." + arg.type + ", " + otherClass + "." + arg.name + ")"); + } else { + w.writeOpenIf("!Arrays.equals(this." + arg.name + ", " + otherClass + "." + arg.name + ")"); + } + w.writeNewLine().writeIndent().writeReturn("false").writeNewLine().writeCloseBlock(true).writeNewLine(); + }); + } + w.writeIndent().writeReturn("true").writeNewLine(); + w.writeCloseBlock(true).writeNewLine(); + w.writeNewLine(); + w.writeIndent().writeOpenFunction("hashCode", List.of(), "int", null).writeNewLine(); + w.writeIndent(); + if (!classMeta.arguments.isEmpty()) { + var primitives = classMeta.arguments.stream().filter(n -> NATIVES.contains(n.type)).collect(Collectors.toList()); + if (!primitives.isEmpty() && classMeta.arguments.size() == 1) { + w.writeReturn(NATIVE_TO_OBJ.get(primitives.get(0).type) + ".hashCode(this." + primitives.get(0).name + ")"); + } else if (!primitives.isEmpty()) { + w.writeLocalAssign("int", "result", NATIVE_TO_OBJ.get(primitives.get(0).type) + ".hashCode(this." + primitives.get(0).name + ")").writeNewLine(); + w.writeIndent(); + } + var tdapi = classMeta.arguments.stream().filter(n -> !NATIVES.contains(n.type)).collect(Collectors.toList()); + int start; + if (!tdapi.isEmpty() && classMeta.arguments.size() == 1) { + w.writeReturn(hashObject("this." + tdapi.get(0).name, tdapi.get(0).type)); + start = 1; + } else { + if (primitives.isEmpty()) { + w.writeLocalAssign("int", "result", hashObject("this." + tdapi.get(0).name, tdapi.get(0).type)).writeNewLine(); + w.writeIndent(); + start = 1; + } else { + start = 0; + } + } + + tdapi.stream().skip(start).forEach(arg -> { + w.writeAssign("result", "result * 31 + (" + hashObject("this." + arg.name, arg.type) + ")").writeNewLine(); + w.writeIndent(); + }); + + if (classMeta.arguments.size() > 1) { + w.writeReturn("result"); + } + } else { + w.writeReturn("CONSTRUCTOR"); + } + w.writeNewLine(); + + w.writeCloseBlock(true).writeNewLine(); + w.writeCloseBlock(true).writeNewLine(); + w.writeNewLine(); + }); + w.write("}"); + } + } + } + + private static String hashObject(String name, String type) { + if (!type.endsWith("[]")) { + return name + " == null ? 0 : " + name + ".hashCode()"; + } else if (type.endsWith("[][]")) { + return "Arrays.deepHashCode(" + name + ")"; + } else { + return "Arrays.hashCode(" + name + ")"; + } + } + + private static void serializeTdApi(JavaWriter w, String argName) { + serializeTdApi(w, argName, true); + } + + private static void serializeTdApi(JavaWriter w, String argName, boolean nullCheck) { + //todo: + } + + private static void serializeNative(JavaWriter w, String argName, String argType) { + serializeNative(w, argName, argType, true); + } + + private static void serializeNative(JavaWriter w, String argName, String argType, boolean nullCheck) { + //todo: + } + + private static void deserializeTdApi(JavaWriter w, String name, String type, Map> cont, Map classes) { + deserializeTdApi(w, name, type, cont, classes, true); + } + + private static void deserializeTdApi(JavaWriter w, String argName, String argType, Map> cont, Map classes, boolean nullCheck) { + if (nullCheck) { + w.writeIndent().writeOpenIf("input.readBoolean()").writeNewLine(); + } + w.writeIndent(); + if (cont.containsKey(argType)) { + w.writeOpenSwitch("input.readInt()").writeNewLine(); + + classes.forEach((className, classMeta) -> { + if (classMeta.containerName.equals(argType) && !className.equals(argType)) { + w.writeIndent().writeOpenSwitchCase(className + ".CONSTRUCTOR"); + w.writeNewLine().writeIndent().writeClassAssign(argName, "new " + className + "(input)"); + w.writeNewLine().writeIndent().writeSwitchBreak().writeNewLine(); + } + }); + + w.writeIndent().writeOpenSwitchDefault().writeNewLine(); + w.writeIndent().writeException("UnsupportedOperationException").writeNewLine().decrementIndentation(); + w.writeCloseBlock(true).writeNewLine(); + } else { + w.writeOpenIf(argType + ".CONSTRUCTOR != input.readInt()"); + w.writeNewLine().writeIndent().writeException("UnsupportedOperationException"); + w.writeNewLine().writeCloseBlock(true).writeNewLine(); + w.writeIndent().writeClassAssign(argName, "new " + argType + "(input)").writeNewLine(); + } + if (nullCheck) { + w.writeCloseBlock(true).writeNewLine(); + } + } + + private static void deserializeNative(JavaWriter w, String name, String type) { + deserializeNative(w, name, type, true); + } + + private static void deserializeNative(JavaWriter w, String name, String type, boolean nullCheck) { + switch (type) { + case "int": { + w.writeIndent().writeClassAssign(name, "input.readInt()").writeNewLine(); + break; + } + case "byte": + case "byte[]": { + if (nullCheck) { + w.writeIndent().writeOpenIf("input.readBoolean()").writeNewLine(); + } + w.writeIndent().writeClassAssign(name, "new byte[input.readInt()]").writeNewLine(); + + w.writeIndent().writeCall("input.readFully", "this." + name).writeNewLine(); + + if (nullCheck) { + w.writeCloseBlock(true).writeNewLine(); + } + break; + } + case "long": { + w.writeIndent().writeClassAssign(name, "input.readLong()").writeNewLine(); + break; + } + case "double": { + w.writeIndent().writeClassAssign(name, "input.readDouble()").writeNewLine(); + break; + } + case "boolean": { + w.writeIndent().writeClassAssign(name, "input.readBoolean()").writeNewLine(); + break; + } + case "String": { + if (nullCheck) { + w.writeIndent().writeOpenIf("input.readBoolean()").writeNewLine(); + } + var tmpName = name.split("\\[", 1)[0] + "Tmp"; + w.writeIndent().writeLocalAssign("byte[]", tmpName, "new byte[input.readInt()]").writeNewLine(); + w.writeIndent().writeCall("input.readFully", tmpName).writeNewLine(); + w.writeIndent().writeClassAssign(name, "new String(" + tmpName + ", StandardCharsets.UTF_8)").writeNewLine(); + + if (nullCheck) { + w.writeCloseBlock(true).writeNewLine(); + } + break; + } + } + } + + private static String extractGeneric(List docs) { + return docs.stream() + .filter(line -> line.strip().startsWith("

Returns ") && line.contains("@link")) + .findFirst().map(line -> line.substring(line.indexOf("{@link ") + 7).split(" ")[0]) + .orElseThrow(); + } + + private static List copyNullableList(List list) { + return list != null ? new ArrayList<>(list) : null; + } + + private static Stream> streamGrouped(Stream in, int pageSize) { + var list = in.collect(Collectors.toList()); + return IntStream.range(0, (list.size() + pageSize - 1) / pageSize) + .mapToObj(i -> list.subList(i * pageSize, Math.min(pageSize * (i + 1), list.size()))); + } + + private static List splitDocs(List docs) { + if (docs == null) return List.of(); + return docs.stream() + .flatMap(line -> wrapLine(line, 70).stream()) + .collect(Collectors.toList()); + } + + + private static List wrapLine(String line, int lineLength) { + if (line.isBlank()) return List.of(); + if (line.length() <= lineLength) return List.of(line); + String[] words = line.split(" "); + List allLines = new ArrayList<>(); + StringBuilder trimmedLine = new StringBuilder(); + for (String word : words) { + if (trimmedLine.length() + 1 + word.length() <= lineLength) { + trimmedLine.append(word).append(" "); + } else { + allLines.add(trimmedLine.toString()); + trimmedLine = new StringBuilder(); + trimmedLine.append(word).append(" "); + } + } + if (trimmedLine.length() > 0) { + allLines.add(trimmedLine.toString()); + } + return allLines; + } + + private static String removeLastChars(String s, int i) { + return s.substring(0, s.length() - i); + } + + private static List extractDoc(List lines, int line) { + int lookBack = 2; + var cursor = lines.listIterator(line - lookBack); + boolean foundDoc = false; + while (cursor.hasPrevious()) { + var prev = cursor.previous(); + if (prev.strip().startsWith("/**")) { + cursor.next(); + foundDoc = true; + break; + } + } + if (!foundDoc) { + return List.of(); + } + + var docs = new ArrayList(); + + while (cursor.hasNext()) { + var current = cursor.next(); + if (current.startsWith("*/")) break; + var currentSplit = List.of(current.split(" ", 2)); + current = currentSplit.get(currentSplit.size() - 1); + if (!current.strip().equals("*")) { + docs.add(current); + } else { + docs.add(""); + } + } + return docs; + } + + private static RuntimeException getHelp() { + System.err.println("Invalid syntax!\nArguments: --source PATH --output PATH [--jdk17 BOOLEAN] [--overwrite BOOLEAN] [--headers PATH]"); + System.exit(1); + return new IllegalStateException(); + } + + static class CurrentArgument { + public String name; + public String type; + public List docs = new ArrayList<>(); + } + + static class TdType { + public int constructorId; + public String containerName; + public List arguments = new ArrayList<>(); + public List docs; + } + + @SuppressWarnings({"UnusedReturnValue", "unused", "resource"}) + public static class JavaWriter implements Closeable { + private final Writer os; + private int depth; + + public JavaWriter(Writer os, int depth) { + this.os = os; + this.depth = depth; + } + + public JavaWriter write(String data) { + try { + os.write(data); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return this; + } + + public JavaWriter writeIndent() { + this.write("\t".repeat(depth)); + return this; + } + + public JavaWriter incrementIndentation() { + depth++; + return this; + } + + public JavaWriter decrementIndentation() { + depth--; + return this; + } + + public JavaWriter writeOpenCustomBlock(String... strings) { + write(String.join(" ", strings) + " {"); + incrementIndentation(); + return this; + } + + public JavaWriter writeOpenDocs() { + write("/**"); + return this; + } + + public JavaWriter writeDocs(String text) { + if (text.lines().count() > 1) { + throw new UnsupportedOperationException("Docs text must be on a single line: " + text); + } + write(" " + ("* " + text).strip()); + return this; + } + + public JavaWriter writeCloseDocs() { + write("**/"); + return this; + } + + public JavaWriter writeNewLine() { + write(System.lineSeparator()); + return this; + } + + public JavaWriter writeClassAssign(String classValue, String localValue) { + write("this." + classValue + " = " + localValue + ";"); + return this; + } + + public JavaWriter writeCloseBlock() { + writeCloseBlock(false); + return this; + } + + public JavaWriter writeCloseBlock(boolean space) { + decrementIndentation(); + if (space) { + writeIndent(); + } + write("}"); + return this; + } + + public JavaWriter writeOpenSwitch(String data) { + incrementIndentation(); + write("switch(" + data + ") {"); + return this; + } + + public JavaWriter writeSwitchBreak() { + decrementIndentation(); + write("break;"); + return this; + } + + public JavaWriter writeOpenSwitchCase(String data) { + incrementIndentation(); + write("case " + data + ":"); + return this; + } + + public JavaWriter writeOpenSwitchDefault() { + incrementIndentation(); + write("default:"); + return this; + } + + public JavaWriter writeSwitchDefault() { + incrementIndentation(); + write("default:"); + return this; + } + + public JavaWriter writeException(String excClass) { + write("throw new " + excClass + "();"); + return this; + } + + public JavaWriter writeOpenIfElse() { + return writeOpenIfElse(false); + } + + public JavaWriter writeOpenIfElse(boolean space) { + decrementIndentation(); + if (space) { + writeIndent(); + } + write("} else {"); + incrementIndentation(); + return this; + } + + public JavaWriter writeLocalAssign(String objectType, String name, String value) { + write(objectType + " " + name + " = " + value + ";"); + return this; + } + + public JavaWriter writeAssign(String name, String value) { + write(name + " = " + value + ";"); + return this; + } + + public JavaWriter writeOpenFor(String start, String cond, String stmt) { + incrementIndentation(); + write("for (" + start + "; " + cond + "; " + stmt + ") {"); + return this; + } + + public JavaWriter writeOpenIf(String cond) { + incrementIndentation(); + write("if (" + cond + ") {"); + return this; + } + + public JavaWriter writeCall(String method, String... args) { + writeCall(method, Stream.of(args)); + return this; + } + + public JavaWriter writeCall(String method, Stream args) { + write(args.collect(Collectors.joining(", ", method + "(", ");"))); + return this; + } + + public JavaWriter writeReturn(String val) { + write("return " + val + ";"); + return this; + } + + public JavaWriter writeDeclare(String name, String type, String flag, String value) { + write(flag + " " + type + " " + name); + if (value != null) { + write(" = " + value); + } + write(";"); + return this; + } + + public JavaWriter writeOpenFunction(String name, List> args, String type, String e) { + incrementIndentation(); + write(args.stream().map(arg -> arg.getKey() + " " + arg.getValue()).collect(Collectors.joining(", ", + "public " + type + " " + name + "(", + ")"))); + if (e != null) { + write(" throws " + e); + } + write(" {"); + return this; + } + + public JavaWriter writeOpenConstructorFunction(String name, List> args, String e) { + incrementIndentation(); + write("public " + name + "("); + var resultArgs = args.stream().map(arg -> arg.getKey() + " " + arg.getValue()).collect(Collectors.joining(", ")); + if (resultArgs.length() > 60) { + var resultArgsSplitted = new ArrayDeque<>(List.of(resultArgs.split(", "))); + incrementIndentation(); + while (!resultArgsSplitted.isEmpty()) { + write(resultArgsSplitted.removeFirst()); + if (!resultArgsSplitted.isEmpty()) { + write(","); + writeNewLine(); + writeIndent(); + } + } + decrementIndentation(); + } else { + write(resultArgs); + } + write(")"); + if (e != null) { + write(" throws " + e); + } + write(" {"); + return this; + } + + @Override + public void close() throws IOException { + os.close(); + } + } +}