tdlib-serializer/transform.java

1178 lines
54 KiB
Java

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<String> NATIVES = List.of("int", "long", "boolean", "String", "double", "byte[]", "byte");
static List<String> PRIMITIVES = List.of("int", "long", "boolean", "double", "byte");
static Map<String, String> 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 {
// Parse arguments
Path headersPath = null;
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();
if (headersPath == null) {
headersPath = Path.of(transform.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent().resolve("headers.txt");
}
boolean java17 = tempJava17;
boolean overwrite = tempOverwrite;
var headers = Files.readString(headersPath, StandardCharsets.UTF_8);
String package_ = null;
Map<String, TdType> functionClasses = new LinkedHashMap<>();
Map<String, TdType> objectClasses = new LinkedHashMap<>();
Map<String, List<String>> containerClassesDocs = new LinkedHashMap<>();
List<CurrentArgument> 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<String> currentClassDocs = new ArrayList<>();
int functionDepth = 0;
// Read the input document
List<String> 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<String, TdType> allClasses = new LinkedHashMap<>();
allClasses.putAll(objectClasses);
allClasses.putAll(functionClasses);
// Write output document
{
List<OpenOption> 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, java17)) {
// Write package
w.write(package_).writeNewLine().writeNewLine();
// Write header
w.write(headers);
w.writeIndent().writeOpenDocs().writeNewLine();
w.writeIndent().writeDocs("This class deserializes TDLib classes").writeNewLine();
w.writeIndent().writeCloseDocs().writeNewLine();
w.writeIndent().writeOpenCustomBlock("public static class Deserializer").writeNewLine().writeNewLine();
w.writeIndent().writeOpenDocs().writeNewLine();
w.writeIndent().writeDocs("The default constructor").writeNewLine();
w.writeIndent().writeCloseDocs().writeNewLine();
w.writeIndent().writeOpenConstructorFunction("Deserializer", List.of(), null).writeNewLine();
w.writeCloseBlock(true).writeNewLine();
w.writeNewLine();
w.writeIndent().writeOpenDocs().writeNewLine();
w.writeIndent().writeDocs("Deserialize the TDLib class").writeNewLine();
w.writeIndent().writeDocs("@param input stream that contain the serialized TDLib class to deserialize").writeNewLine();
w.writeIndent().writeDocs("@return the deserialized TDLib class").writeNewLine();
w.writeIndent().writeDocs("@throws IOException the deserialization failed").writeNewLine();
w.writeIndent().writeCloseDocs().writeNewLine();
w.writeIndent().writeOpenFunction("deserialize", List.of(Map.entry("DataInput", "input")), "static Object", "IOException").writeNewLine();
w.writeIndent().writeOpenSwitchExpression(null, null, false, "input.readInt()").writeNewLine();
allClasses.forEach((className, x) -> {
w.writeIndent().writeSwitchCaseAndYield(className + ".CONSTRUCTOR", "new " + className + "(input)").writeNewLine();
});
w.writeIndent().writeOpenSwitchDefault().writeNewLine();
w.writeIndent().writeYieldException("UnsupportedOperationException").writeNewLine();
w.writeIndent().writeCloseCase().writeNewLine();
w.writeCloseSwitchExpression().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<String>();
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("").writeNewLine();
} else {
w.writeOpenCustomBlock("public abstract static class " + containerClassName + " extends Object").writeNewLine();
}
w.writeNewLine();
w.writeIndent().writeOpenDocs().writeNewLine();
w.writeIndent().writeDocs("Default class constructor.").writeNewLine();
w.writeIndent().writeCloseDocs().writeNewLine();
w.writeIndent().writeOpenConstructorFunction(containerClassName, List.of(), null).writeCloseBlock().writeNewLine().writeNewLine();
w.writeCloseBlock(true).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).writeNewLine();
w.writeNewLine();
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.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.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.type, a.name)).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().writeNewLine();
classMeta.docs.forEach(doc -> w.writeIndent().writeDocs(doc).writeNewLine());
w.writeIndent().writeDocs("").writeNewLine();
w.writeIndent().writeDocs("@param input Serialized input").writeNewLine();
w.writeIndent().writeDocs("@throws IOException the deserialization failed").writeNewLine();
w.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(className + ".CONSTRUCTOR").writeNewLine();
w.writeCloseBlock(true).writeNewLine();
w.writeNewLine();
w.writeIndent().writeOpenDocs().writeNewLine();
w.writeIndent().writeDocs("Serialize the TDLib class").writeNewLine();
w.writeIndent().writeDocs("@param output output data stream").writeNewLine();
w.writeIndent().writeDocs("@throws IOException the serialization failed").writeNewLine();
w.writeIndent().writeCloseDocs().writeNewLine();
w.writeIndent().writeOpenFunction("serialize", List.of(Map.entry("DataOutput", "output")), "void", "IOException").writeNewLine();
w.writeIndent().writeCall("output.writeInt", className + ".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, arg.name + "[i]", baseArgType, 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, arg.name + "[i][j]", baseArgType, 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.name + ", " + 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 -> PRIMITIVES.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 -> !PRIMITIVES.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(className + ".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) {
if (nullCheck) {
w.writeIndent().writeOpenIf("this." + argName + " == null").writeNewLine();
w.writeIndent().writeCall("output.writeBoolean", "false").writeNewLine();
w.writeOpenIfElse(true).writeNewLine();
w.writeIndent().writeCall("output.writeBoolean", "true").writeNewLine();
}
w.writeIndent().writeCall("this." + argName + ".serialize", "output").writeNewLine();
if (nullCheck) {
w.writeCloseBlock(true).writeNewLine();
}
}
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) {
switch (argType) {
case "int":
w.writeIndent().writeCall("output.writeInt", "this." + argName).writeNewLine();
break;
case "byte[]":
case "byte":
if (nullCheck) {
w.writeIndent().writeOpenIf("this." + argName + " == 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." + argName + ".length").writeNewLine();
w.writeIndent().writeCall("output.write", "this." + argName).writeNewLine();
if (nullCheck) {
w.writeCloseBlock(true).writeNewLine();
}
break;
case "long":
w.writeIndent().writeCall("output.writeLong", "this." + argName).writeNewLine();
break;
case "double":
w.writeIndent().writeCall("output.writeDouble", "this." + argName).writeNewLine();
break;
case "boolean":
w.writeIndent().writeCall("output.writeBoolean", "this." + argName).writeNewLine();
break;
case "String":
if (nullCheck) {
w.writeIndent().writeOpenIf("this." + argName + " == null").writeNewLine();
w.writeIndent().writeCall("output.writeBoolean", "false").writeNewLine();
w.writeIndent().writeOpenIfElse(true).writeNewLine();
w.writeIndent().writeCall("output.writeBoolean", "true").writeNewLine();
}
var tmpName = argName.split("\\[", 2)[0] + "Tmp";
w.writeIndent().writeLocalAssign("byte[]", tmpName, "this." + argName + ".getBytes(StandardCharsets.UTF_8)").writeNewLine();
w.writeIndent().writeCall("output.writeInt", tmpName + ".length").writeNewLine();
w.writeIndent().writeCall("output.write", tmpName).writeNewLine();
if (nullCheck) {
w.writeCloseBlock(true).writeNewLine();
}
break;
default: throw new UnsupportedOperationException(argName + ":" + argType);
}
}
private static void deserializeTdApi(JavaWriter w, String name, String type, Map<String, List<String>> cont, Map<String, TdType> classes) {
deserializeTdApi(w, name, type, cont, classes, true);
}
private static void deserializeTdApi(JavaWriter w, String argName, String argType, Map<String, List<String>> cont, Map<String, TdType> classes, boolean nullCheck) {
if (nullCheck) {
w.writeIndent().writeOpenIf("input.readBoolean()").writeNewLine();
}
w.writeIndent();
if (cont.containsKey(argType)) {
w.writeOpenSwitchExpression(null, "this." + argName, true, "input.readInt()").writeNewLine();
classes.forEach((className, classMeta) -> {
if (classMeta.containerName.equals(argType) && !className.equals(argType)) {
w.writeIndent().writeSwitchCaseAndYield(className + ".CONSTRUCTOR", "new " + className + "(input)").writeNewLine();
}
});
w.writeIndent().writeOpenSwitchDefault().writeNewLine();
w.writeIndent().writeYieldException("UnsupportedOperationException").writeNewLine();
w.writeIndent().writeCloseCase().writeNewLine();
w.writeCloseSwitchExpression().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("\\[", 2)[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<String> docs) {
return docs.stream()
.filter(line -> line.strip().startsWith("<p> Returns ") && line.contains("@link"))
.findFirst().map(line -> line.substring(line.indexOf("{@link ") + 7).split(" ")[0])
.orElseThrow();
}
private static List<String> copyNullableList(List<String> list) {
return list != null ? new ArrayList<>(list) : null;
}
private static <X> Stream<List<X>> streamGrouped(Stream<X> 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<String> splitDocs(List<String> docs) {
if (docs == null) return List.of();
return docs.stream()
.flatMap(line -> wrapLine(line, 70).stream())
.collect(Collectors.toList());
}
private static List<String> wrapLine(String line, int lineLength) {
if (line.isBlank()) return List.of();
if (line.length() <= lineLength) return List.of(line);
String[] words = line.split(" ");
List<String> 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<String> extractDoc(List<String> 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<String>();
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 [--java17 BOOLEAN] [--overwrite BOOLEAN] [--headers PATH]");
System.exit(1);
return new IllegalStateException();
}
static class CurrentArgument {
public String name;
public String type;
public List<String> docs = new ArrayList<>();
}
static class TdType {
public int constructorId;
public String containerName;
public List<CurrentArgument> arguments = new ArrayList<>();
public List<String> docs;
}
@SuppressWarnings({"UnusedReturnValue", "unused", "resource"})
public static class JavaWriter implements Closeable {
private final Writer os;
private final boolean java17;
private int depth;
private boolean insideSwitchExpression;
private String switchExpressionVarName;
public JavaWriter(Writer os, int depth, boolean java17) {
this.os = os;
this.depth = depth;
this.java17 = java17;
}
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 writeOpenSwitchExpression(String varType, String varName, boolean alreadyExistingVar, String data) {
if (insideSwitchExpression) throw new UnsupportedOperationException();
insideSwitchExpression = true;
this.switchExpressionVarName = varName;
if (java17) {
if (switchExpressionVarName == null) {
write("return switch (" + data + ") {");
} else if (alreadyExistingVar) {
write(varName + " = switch (" + data + ") {");
} else {
write((varType == null ? "var" : varType) + " " + varName + " = switch (" + data + ") {");
}
} else {
if (!alreadyExistingVar && varName != null) {
write((varType == null ? "Object" : varType) + " " + varName + ";");
writeNewLine();
writeIndent();
}
write("switch(" + data + ") {");
}
incrementIndentation();
return this;
}
public JavaWriter writeSwitchBreak() {
if (insideSwitchExpression) throw new UnsupportedOperationException();
decrementIndentation();
write("break;");
return this;
}
public JavaWriter writeYield(String expression) {
if (!insideSwitchExpression) throw new UnsupportedOperationException();
if (java17) {
write("yield " + expression + ";");
} else if (this.switchExpressionVarName == null) {
write("return " + expression + ";");
} else {
write(this.switchExpressionVarName + " = " + expression + "; break;");
}
decrementIndentation();
return this;
}
public JavaWriter writeYieldException(String expression) {
if (!insideSwitchExpression) throw new UnsupportedOperationException();
write("throw new " + expression + "();");
decrementIndentation();
return this;
}
public JavaWriter writeCloseCase() {
write("}");
return this;
}
public JavaWriter writeCloseSwitchExpression() {
decrementIndentation();
writeIndent();
if (!insideSwitchExpression) throw new UnsupportedOperationException();
insideSwitchExpression = false;
if (java17) {
write("};");
} else {
write("}");
}
return this;
}
public JavaWriter writeOpenSwitchCase(String data) {
if (java17 && insideSwitchExpression) {
write("case " + data + " -> {");
} else {
write("case " + data + ":");
}
incrementIndentation();
return this;
}
public JavaWriter writeSwitchCaseAndYield(String caseExpression, String yieldExpression) {
if (java17 && insideSwitchExpression) {
write("case " + caseExpression + " -> ");
} else {
write("case " + caseExpression + ": ");
}
if (!insideSwitchExpression) throw new UnsupportedOperationException();
if (java17) {
write(yieldExpression + ";");
} else if (this.switchExpressionVarName == null) {
write("return " + yieldExpression + ";");
} else {
write(this.switchExpressionVarName + " = " + yieldExpression + "; break;");
}
return this;
}
public JavaWriter writeOpenSwitchDefault() {
if (java17 && insideSwitchExpression) {
write("default -> {");
} else {
write("default: {");
}
incrementIndentation();
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<String> 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<Map.Entry<String, String>> 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<Map.Entry<String, String>> 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();
}
}
}