315 lines
12 KiB
Java
315 lines
12 KiB
Java
|
package io.github.lonamiwebs.overgram.generator;
|
||
|
|
||
|
import io.github.lonamiwebs.overgram.parser.TLArg;
|
||
|
import io.github.lonamiwebs.overgram.parser.TLObject;
|
||
|
|
||
|
import java.io.*;
|
||
|
import java.util.*;
|
||
|
|
||
|
public class Generator {
|
||
|
|
||
|
private static final String VARIABLE_SUFFIX = "$";
|
||
|
|
||
|
public static void generateJava(
|
||
|
final List<TLObject> types, final List<TLObject> 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, types);
|
||
|
}
|
||
|
try (final Writer writer = new BufferedWriter(new FileWriter(typesFile))) {
|
||
|
writeCode(writer, "Types", null, types);
|
||
|
}
|
||
|
try (final Writer writer = new BufferedWriter(new FileWriter(functionsFile))) {
|
||
|
writeCode(writer, "Functions", "TLRequest", functions);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void writeAbstract(final Writer writer, final List<TLObject> objects) throws IOException {
|
||
|
writer.write("package io.github.lonamiwebs.overgram.tl;\n");
|
||
|
writer.write("@SuppressWarnings(\"unused\")\n");
|
||
|
writer.write("public class Abstract {\n");
|
||
|
for (final Map.Entry<String, List<String>> 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(TLObject.toCamelCase(name));
|
||
|
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<TLObject> objects) 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("@SuppressWarnings(\"ALL\")\n");
|
||
|
writer.write("public class ");
|
||
|
writer.write(className);
|
||
|
writer.write(" {\n");
|
||
|
for (final Map.Entry<String, List<TLObject>> 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(TLObject.toCamelCase(object.name));
|
||
|
writer.write(" extends ");
|
||
|
if (extendsName == null) {
|
||
|
writer.write("Abstract.");
|
||
|
writer.write(TLObject.toCamelCase(object.type));
|
||
|
} else {
|
||
|
writer.write(extendsName);
|
||
|
}
|
||
|
writer.write(" {\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(TLObject.toCamelCase(object.name));
|
||
|
writer.write(' ');
|
||
|
writer.write(arg.javaName());
|
||
|
writer.write('(');
|
||
|
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(TLObject.toCamelCase(object.name));
|
||
|
writer.write("() {}\n");
|
||
|
|
||
|
writeSerialize(writer, object);
|
||
|
writeDeserialize(writer, object);
|
||
|
|
||
|
writer.write("}\n");
|
||
|
}
|
||
|
if (!nsObjects.getKey().isEmpty()) {
|
||
|
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");
|
||
|
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(" + VARIABLE_SUFFIX + i + ");");
|
||
|
} else {
|
||
|
writer.write("writer.write(");
|
||
|
writer.write(arg.name);
|
||
|
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) {\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(" = ");
|
||
|
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":
|
||
|
// TODO handle int128 and int256 properly
|
||
|
case "int128":
|
||
|
case "int256":
|
||
|
writer.write("reader.readLong();");
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (arg.flag != null) {
|
||
|
writer.write(" }\n");
|
||
|
} else {
|
||
|
writer.write('\n');
|
||
|
}
|
||
|
}
|
||
|
writer.write("}\n");
|
||
|
}
|
||
|
|
||
|
private static Map<String, List<TLObject>> byNamespace(final Iterable<TLObject> objects) {
|
||
|
final Map<String, List<TLObject>> result = new HashMap<>();
|
||
|
for (final TLObject object : objects) {
|
||
|
result.computeIfAbsent(object.namespace, k -> new ArrayList<>()).add(object);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
private static Map<String, List<String>> stringsByNamespace(final Iterable<String> strings) {
|
||
|
final Map<String, List<String>> 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<String> uniqueTypes(final List<TLObject> objects) {
|
||
|
final Set<String> 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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|