diff --git a/__main__.py b/__main__.py index 0598114..30036f2 100644 --- a/__main__.py +++ b/__main__.py @@ -7,6 +7,72 @@ 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 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) + + return result + + +def extract_doc(lines: typing.List[str], line: int) -> typing.List[str]: + line = next( + n + for n in range(line - 2, 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: @@ -241,6 +307,7 @@ def main(input_path: str, output_path: str, headers_path: str): 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() @@ -252,11 +319,12 @@ def main(input_path: str, output_path: str, headers_path: str): container_classes: typing.List[str] = [] # [class_name, ...] - current_arguments: typing.Optional[typing.List[typing.Tuple[str, str]]] = None + current_arguments: typing.Optional[typing.List[typing.Tuple[str, str, typing.List[str]]]] = None # [(arg_name, arg_type), ...] - for line in data_input.readlines(): - line = line.strip() + lines = list(map(str.strip, data_input.readlines())) + + for no, line in enumerate(lines): keywords = line.split() if not keywords: @@ -285,7 +353,8 @@ def main(input_path: str, output_path: str, headers_path: str): if inside_object_class and keywords[-1] == "}": inside_object_class = False - object_classes[current_class_name] = (current_constructor, container_class_name, current_arguments) + 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 @@ -294,7 +363,8 @@ def main(input_path: str, output_path: str, headers_path: str): if inside_function_class and keywords[0] == "}": inside_function_class = False - function_classes[current_class_name] = (current_constructor, container_class_name, current_arguments) + 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 @@ -303,7 +373,7 @@ def main(input_path: str, output_path: str, headers_path: str): if inside_function_class or inside_object_class: if len(keywords) == 3 and keywords[-1].endswith(";"): - current_arguments.append((keywords[1], keywords[2][:-1])) + current_arguments.append((keywords[1], keywords[2][:-1], extract_doc(lines, no))) continue if len(keywords) == 7 and keywords[4] == "CONSTRUCTOR": @@ -330,6 +400,7 @@ def main(input_path: str, output_path: str, headers_path: str): current_class_name = keywords[-4] current_arguments = [] inside_object_class = True + current_class_docs = extract_doc(lines, no) container_class_name = keywords[5] continue @@ -337,6 +408,7 @@ def main(input_path: str, output_path: str, headers_path: str): current_class_name = keywords[-4] current_arguments = [] inside_function_class = True + current_class_docs = extract_doc(lines, no) container_class_name = keywords[5] continue @@ -405,10 +477,21 @@ def main(input_path: str, output_path: str, headers_path: str): for class_name, class_meta in classes.items(): output.indent() output.open_custom_block("public static class", class_name, "extends", class_meta[1]) - output.newline() - for arg_type, arg_name in class_meta[2]: + 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() @@ -425,24 +508,54 @@ def main(input_path: str, output_path: str, headers_path: str): output.close_block() output.newline() + output.indent() + output.open_docs() + + for doc in class_meta[3]: + output.newline() + output.indent() + output.write_docs(doc) + + output.newline() + output.indent() + output.write_docs() + + for arg_name, arg_type, docs in class_meta[2]: + 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, class_meta[2]) + 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]: + 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.newline() output.indent() output.open_constructor_function(class_name, [("DataInput", "input")], "IOException") output.newline() - for arg_type, arg_name in class_meta[2]: + for arg_type, arg_name, _ in class_meta[2]: if arg_type in natives: deserialize_native(output, arg_name, arg_type) @@ -529,7 +642,7 @@ def main(input_path: str, output_path: str, headers_path: str): output.call("output.writeInt", f"CONSTRUCTOR") output.newline() - for arg_type, arg_name in class_meta[2]: + for arg_type, arg_name, _ in class_meta[2]: if arg_type in natives: serialize_native(output, arg_type, arg_name) @@ -609,6 +722,116 @@ def main(input_path: str, output_path: str, headers_path: str): 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() diff --git a/code_writer.py b/code_writer.py index 97b000c..205f54d 100644 --- a/code_writer.py +++ b/code_writer.py @@ -19,6 +19,18 @@ class CodeWriter: 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 + ";") @@ -60,6 +72,9 @@ class CodeWriter: 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 + ") {") diff --git a/headers.txt b/headers.txt index b6bdad2..850e35a 100644 --- a/headers.txt +++ b/headers.txt @@ -6,36 +6,13 @@ import java.lang.IllegalStateException; import java.io.IOException; import java.io.DataInput; import java.util.Arrays; +import java.util.Objects; public class TdApi { public abstract static class Object { public native String toString(); - @Override - public boolean equals(java.lang.Object o) { - if (o == null) return false; - if (o == this) return true; - if (o instanceof TdApi.Object) { - try { - return Arrays.equals(this.serialize(), ((TdApi.Object) o).serialize()); - } catch (IOException ex) { - ex.printStackTrace(); - } - } - return false; - } - - @Override - public int hashCode() { - try { - return Arrays.hashCode(this.serialize()); - } catch (IOException ex) { - ex.printStackTrace(); - } - return 0; - } - public abstract int getConstructor(); public byte[] serialize() throws IOException {