package io.github.lonamiwebs.overgram.generator; import io.github.lonamiwebs.overgram.parser.TLArg; import io.github.lonamiwebs.overgram.parser.TLObject; import io.github.lonamiwebs.overgram.utils.Utils; import java.io.*; import java.util.*; public class Generator { private static final String VARIABLE_SUFFIX = "$"; public static void generateJava( final int layer, final List types, final List functions, final File abstractsFile, final File typesFile, final File functionsFile) throws IOException { makeParents(abstractsFile, typesFile, functionsFile); try (final Writer writer = new BufferedWriter(new FileWriter(abstractsFile))) { writeAbstract(writer, layer, types); } try (final Writer writer = new BufferedWriter(new FileWriter(typesFile))) { writeCode(writer, "Types", "%s", types, false); } try (final Writer writer = new BufferedWriter(new FileWriter(functionsFile))) { writeCode(writer, "Functions", "TLRequest<%s>", functions, true); } } private static void writeAbstract(final Writer writer, final int layer, final List objects) throws IOException { writer.write("package io.github.lonamiwebs.overgram.tl;\n"); writer.write("@SuppressWarnings(\"unused\")\n"); writer.write("public class Abstract {\n"); writer.write("public static final int LAYER = " + layer + ";\n"); for (final Map.Entry> nsNames : stringsByNamespace(uniqueTypes(objects)).entrySet()) { if (!nsNames.getKey().isEmpty()) { writer.write("public static class "); writer.write(nsNames.getKey()); writer.write(" {\n"); } for (final String name : nsNames.getValue()) { writer.write("public static abstract class "); writer.write(Utils.toCamelCase(name, true)); writer.write(" extends TLObject {}\n"); } if (!nsNames.getKey().isEmpty()) { writer.write("}\n"); } } writer.write("}\n"); } private static void writeCode( final Writer writer, final String className, final String extendsName, final List objects, final boolean functions) throws IOException { writer.write("package io.github.lonamiwebs.overgram.tl;\n"); writer.write("import io.github.lonamiwebs.overgram.utils.BinaryReader;\n"); writer.write("import io.github.lonamiwebs.overgram.utils.BinaryWriter;\n"); writer.write("import java.util.List;\n"); writer.write("import java.math.BigInteger;\n"); writer.write("@SuppressWarnings(\"ALL\")\n"); writer.write("public class "); writer.write(className); writer.write(" {\n"); final boolean nested = extendsName.contains("<"); for (final Map.Entry> nsObjects : byNamespace(objects).entrySet()) { if (!nsObjects.getKey().isEmpty()) { writer.write("public static class "); writer.write(nsObjects.getKey()); writer.write(" {\n"); } for (final TLObject object : nsObjects.getValue()) { writer.write("public static class "); writer.write(Utils.toCamelCase(object.name, true)); writer.write(" extends "); writer.write(String.format(extendsName, object.typeAsArg().javaType(nested))); writer.write(" {\n"); writer.write("public static final int CONSTRUCTOR_ID = " + object.code + ";\n"); for (int i = 0; i < object.args.size(); ++i) { final TLArg arg = object.args.get(i); if (arg.genericDefinition || arg.flags) { continue; } writer.write("private "); writer.write(arg.javaType()); writer.write(" " + VARIABLE_SUFFIX + i); writer.write(";\n"); // Use the builder pattern because Java is stupid and has no optional named arguments. // Objects will be created with nothing set and the arguments set with .argument(value). writer.write("public "); writer.write(Utils.toCamelCase(object.name, true)); writer.write(' '); writer.write(arg.javaName()); writer.write("(final "); writer.write(arg.javaType()); writer.write(" value) {\n"); writer.write(VARIABLE_SUFFIX + i); writer.write(" = value;\n"); writer.write("return this;\n"); writer.write("}\n"); // Also allow getting the values in a similar manner without input value .argument() writer.write("public "); writer.write(arg.javaType()); writer.write(' '); writer.write(arg.javaName()); writer.write("() {\n"); writer.write("return " + VARIABLE_SUFFIX + i); writer.write(";\n"); writer.write("}\n"); } writer.write("public "); writer.write(Utils.toCamelCase(object.name, true)); writer.write("() {}\n"); writeSerialize(writer, object); writeDeserialize(writer, object); if (functions) { writeReadResult(writer, object); } writer.write("}\n"); } if (!nsObjects.getKey().isEmpty()) { writer.write("}\n"); } } if (!functions) { writer.write("public static TLObject getFromId(final int id) throws ClassNotFoundException {\n"); writer.write("switch (id) {\n"); for (final TLObject object : objects) { writer.write("case " + object.code); writer.write(":\n"); writer.write("return new Types."); writer.write(Utils.toCamelCase(object.fullname, true)); writer.write("();\n"); } writer.write("default:\n"); writer.write("throw new ClassNotFoundException(Integer.toHexString(id));\n"); writer.write("}\n"); writer.write("}\n"); } writer.write("}\n"); } private static void writeSerialize(final Writer writer, final TLObject object) throws IOException { writer.write("public void serialize(final BinaryWriter writer) {\n"); writer.write("writer.write(CONSTRUCTOR_ID);\n"); for (final TLArg arg : object.args) { if (!arg.genericDefinition && arg.flags) { writer.write("int "); writer.write(arg.name); writer.write(" = 0;\n"); } } for (int i = 0; i < object.args.size(); ++i) { final TLArg arg = object.args.get(i); if (!arg.genericDefinition && arg.flag != null) { writer.write("if (" + VARIABLE_SUFFIX + i); writer.write(arg.javaSetCheck()); writer.write(") { "); writer.write(arg.flag.flagName); writer.write(" |= " + (1 << arg.flag.flagIndex)); writer.write("; }\n"); } } for (int i = 0; i < object.args.size(); ++i) { final TLArg arg = object.args.get(i); if (arg.genericDefinition) { continue; } if (arg.flag != null) { writer.write("if (" + VARIABLE_SUFFIX + i); writer.write(arg.javaSetCheck()); writer.write(") { "); } if (arg.flags) { writer.write("writer.write("); writer.write(arg.name); writer.write(");"); } else { writer.write("writer.write(" + VARIABLE_SUFFIX + i); if (arg.bigIntSize() != 0) { writer.write(", " + arg.bigIntSize()); } writer.write(");"); } if (arg.flag != null) { writer.write(" }\n"); } else { writer.write('\n'); } } writer.write("}\n"); } private static void writeDeserialize(final Writer writer, final TLObject object) throws IOException { writer.write("public void deserialize(final BinaryReader reader) throws ClassNotFoundException {\n"); for (int i = 0; i < object.args.size(); ++i) { final TLArg arg = object.args.get(i); if (arg.genericDefinition) { continue; } if (arg.flags) { writer.write("int "); writer.write(arg.name); writer.write(" = reader.readInt();\n"); continue; } if (arg.flag != null) { writer.write("if (("); writer.write(arg.flag.flagName); writer.write(" & " + (1 << arg.flag.flagIndex)); writer.write(") != 0) { "); } // TODO The only nested type we handle is Vector<>, avoid that // TODO We don't handle boxed vs. unboxed here, either writer.write(VARIABLE_SUFFIX + i); writer.write(" = "); writeRead(writer, arg); if (arg.flag != null) { writer.write(" }\n"); } else { writer.write('\n'); } } writer.write("}\n"); } private static void writeRead(final Writer writer, final TLArg arg) throws IOException { if (arg.types.get(0).equalsIgnoreCase("vector")) { switch (arg.types.get(1)) { case "int": writer.write("reader.readIntList();"); break; case "long": writer.write("reader.readLongList();"); break; case "string": writer.write("reader.readStringList();"); break; case "bytes": writer.write("reader.readBytesList();"); break; case "int128": case "int256": case "double": case "Bool": case "true": throw new UnsupportedOperationException("vector of " + arg.types.get(1)); default: writer.write("("); writer.write(arg.javaType()); writer.write(") reader.readTlList();"); break; } } else { switch (arg.types.get(0)) { case "int": writer.write("reader.readInt();"); break; case "long": writer.write("reader.readLong();"); break; case "int128": writer.write("reader.readInt128();"); break; case "int256": writer.write("reader.readInt256();"); break; case "double": writer.write("reader.readDouble();"); break; case "Bool": case "true": writer.write("reader.readBoolean();"); break; case "string": writer.write("reader.readString();"); break; case "bytes": writer.write("reader.readBytes();"); break; default: writer.write("("); writer.write(arg.javaType()); writer.write(") reader.readTl();"); break; } } } private static void writeReadResult(final Writer writer, final TLObject object) throws IOException { writer.write("public "); writer.write(object.typeAsArg().javaType(true)); writer.write(" readResult(final BinaryReader reader) throws ClassNotFoundException {\n"); writer.write("return "); writeRead(writer, object.typeAsArg()); writer.write('\n'); writer.write("}\n"); } private static Map> byNamespace(final Iterable objects) { final Map> result = new HashMap<>(); for (final TLObject object : objects) { result.computeIfAbsent(object.namespace, k -> new ArrayList<>()).add(object); } return result; } private static Map> stringsByNamespace(final Iterable strings) { final Map> result = new HashMap<>(); String[] tmp; String namespace; String name; for (final String string : strings) { tmp = string.split("\\."); if (tmp.length == 1) { namespace = ""; name = tmp[0]; } else { namespace = tmp[0]; name = tmp[1]; } result.computeIfAbsent(namespace, k -> new ArrayList<>()).add(name); } return result; } private static Set uniqueTypes(final List objects) { final Set result = new HashSet<>(); for (final TLObject object : objects) { result.add(object.type); } return result; } private static void makeParents(final File... files) throws IOException { for (File file : files) { file = file.getAbsoluteFile().getParentFile(); if (!file.isDirectory()) { if (!file.mkdirs()) { throw new IOException("Failed to create " + file); } } } } }