package xapi.dev.model;
import xapi.annotation.model.ClientToServer;
import xapi.annotation.model.ServerToClient;
import xapi.collect.X_Collect;
import xapi.collect.api.ClassTo;
import xapi.collect.api.Fifo;
import xapi.collect.api.IntTo;
import xapi.collect.api.ObjectTo;
import xapi.collect.api.StringTo;
import xapi.dev.model.ModelField.GetterMethod;
import xapi.dev.source.ClassBuffer;
import xapi.dev.source.MethodBuffer;
import xapi.dev.source.SourceBuilder;
import xapi.fu.Out1;
import xapi.source.api.IsType;
import xapi.util.api.ConvertsTwoValues;
import static xapi.source.X_Source.primitiveToObject;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
public class ModelGenerator {
public final static class ModelSerializerResult {
public MethodBuffer clientDeserializer;
public MethodBuffer clientSerializer;
public MethodBuffer clientInstantiator;
public MethodBuffer serverDeserializer;
public MethodBuffer serverSerializer;
public MethodBuffer serverInstantiator;
}
private static class DefaultProvider {
private final Predicate<String> matcher;
private final ConvertsTwoValues<MethodBuffer, GetterMethod, String> initializer;
private DefaultProvider(Predicate<String> matcher, ConvertsTwoValues<MethodBuffer, GetterMethod, String> initializer) {
this.matcher = matcher;
this.initializer = initializer;
}
public boolean tryMatch(String type) {
return matcher.test(type);
}
}
private final ClassBuffer cb;
private List<DefaultProvider> providers;
public ModelGenerator(final SourceBuilder<?> builder) {
this.cb = builder.getClassBuffer();
providers = new ArrayList<>();
initializeDefaults(providers);
}
protected void initializeDefaults(List<DefaultProvider> providers) {
providers.add(
new DefaultProvider(
new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("[]");
}
}, new ConvertsTwoValues<MethodBuffer, GetterMethod, String>() {
@Override
public String convert(MethodBuffer mb, GetterMethod data) {
String datatype = mb.addImport(data.returnType.getQualifiedName());
final String array = mb.addImport(Array.class);
return
"return ("+ datatype +")" + // we need to cast because array.newInstance returns Object
array + ".newInstance(" + datatype.replace("[]", "") + // only replaces one [], thus allowing int[][] to become Array.newInstance(int[].class, 0);
".class, 0);";
}
}
)
);
providers.add(newCollectionType(IntTo.class, "newList"));
providers.add(newCollectionType(StringTo.class, "newStringMap"));
providers.add(newCollectionType(ClassTo.class, "newClassMap"));
providers.add(newCollectionType(IntTo.Many.class, "newIntMultiMap"));
providers.add(newCollectionType(StringTo.Many.class, "newStringMultiMap"));
providers.add(newCollectionType(ClassTo.Many.class, "newClassMultiMap"));
providers.add(newMapType(ObjectTo.class, "newMap"));
providers.add(newMapType(ObjectTo.Many.class, "newMultiMap"));
// TODO consider allowing primitive wrapper types to be initialized to non-null
// This should be done via a NotNull annotation.
// providers.add(
// new DefaultProvider(
// new Predicate<String>() {
// @Override
// public boolean test(String s) {
// switch (s.toString()) {
// case "java.lang.Byte":
// case "java.lang.Short":
// case "java.lang.Character":
// case "java.lang.Integer":
// case "java.lang.Long":
// case "java.lang.Float":
// case "java.lang.Double":
// return true;
// }
// return false;
// }
// },
// new ConvertsTwoValues<MethodBuffer, GetterMethod, String>() {
// @Override
// public String convert(MethodBuffer one, GetterMethod two) {
// return "return new "+two.returnType.getSimpleName()+"(0);";
// }
// }
// )
// );
providers.add(
new DefaultProvider(
new Predicate<String>() {
@Override
public boolean test(String s) {
return "java.lang.Boolean".equals(s);
}
},
new ConvertsTwoValues<MethodBuffer, GetterMethod, String>() {
@Override
public String convert(MethodBuffer one, GetterMethod two) {
return "return false;";
}
}
)
);
}
protected DefaultProvider newCollectionType(final Class<?> cls, final String method) {
return new DefaultProvider(
new Predicate<String>() {
@Override
public boolean test(String s) {
return s.equals(cls.getCanonicalName());
}
}, new ConvertsTwoValues<MethodBuffer, GetterMethod, String>() {
@Override
public String convert(MethodBuffer one, GetterMethod two) {
final String collect = one.addImport(X_Collect.class);
assert two.generics.length == 1 : "Expected only one generic type for a collections class "+cls;
String component = one.addImport(two.generics[0].getQualifiedName());
return "return "+collect+"."+method+"("+component+".class);";
}
}
);
}
protected DefaultProvider newMapType(final Class<?> cls, final String method) {
return new DefaultProvider(
new Predicate<String>() {
@Override
public boolean test(String s) {
return s.equals(cls.getCanonicalName());
}
}, new ConvertsTwoValues<MethodBuffer, GetterMethod, String>() {
@Override
public String convert(MethodBuffer one, GetterMethod two) {
final String collect = one.addImport(X_Collect.class);
assert two.generics.length == 2 : "Expected exactly two generic types for a map class "+cls;
String keyType = one.addImport(two.generics[0].getQualifiedName());
String valueType = one.addImport(two.generics[1].getQualifiedName());
return "return "+collect+"."+method+"("+keyType+".class, "+valueType+".class);";
}
}
);
}
public void createFactory(final String qualifiedSourceName) {
cb.createMethod("static "+qualifiedSourceName+" newInstance()")
.println("return new "+cb.getSimpleName()+"();");
}
public MethodBuffer createMethod(final String returnType, final String methodName, final String params) {
return cb.createMethod(
"public " +returnType +" " + methodName + "( "+params +" )");
}
public void setSuperClass(final String qualifiedSourceName) {
cb.setSuperClass(qualifiedSourceName);
}
public void generateModel(final IsType type, final HasModelFields fields) {
// Write getters for all fields.
for (final ModelField field : fields.getAllFields()) {
if (field.getType() == null) {
throw new RuntimeException("No type found for field "+field.getName()+" in "+type+". " +
"Perhaps you are lacking a getter/model annotation? Check that get/set method names match.");
}
for (final GetterMethod getter : field.getGetters()) {
final String qualified = getter.returnType.getQualifiedName();
final String imported = cb.addImport(qualified);
final MethodBuffer mb = createMethod(imported,
getter.methodName, "");
final IsType returnType = getter.returnType;
if (returnType.isPrimitive() && !returnType.getSimpleName().contains("[]")) {
mb.print("return this.<" + primitiveToObject(imported)+">getProperty(\"" );
mb.println(field.getName() + "\", "+ getDefaultValue(imported) +");");
} else {
mb.print("return this.<" + imported + ">getProperty(\"" + field.getName() + "\"");
for (DefaultProvider defaultProvider : providers) {
if (defaultProvider.tryMatch(qualified)) {
final String provider = mb.addImport(Out1.class);
mb.print(", ")
.print("new ").print(provider).print("<").print(imported).println(">() {")
.indent()
.print("public ").print(imported).println(" out1() {")
.indent()
.println(defaultProvider.initializer.convert(mb, getter))
.outdent()
.println("}")
.outdent()
.println("}")
;
break;
}
}
mb.println(");");
}
}
}
}
private String getDefaultValue(final String type) {
switch (type) {
case "boolean":
return "false";
case "char":
return "'\0'";
case "byte":
case "short":
return "0";
case "long":
return "0L";
case "float":
return "0f";
case "double":
return "0.";
}
return "null";
}
public ModelSerializerResult generateSerializers(final IsType type, final HasModelFields fields) {
final Iterator<ModelField> serializable = fields.getAllSerializable().iterator();
if (!serializable.hasNext()) {
return null;
}
final Fifo<ModelField> toServer = X_Collect.newFifo();
final Fifo<ModelField> toClient = X_Collect.newFifo();
final ServerToClient clientReceives = fields.getDefaultToClient();
final ClientToServer serverReceives = fields.getDefaultToServer();
final boolean toClientEnabled = clientReceives != null && clientReceives.enabled();
final boolean toServerEnabled = serverReceives != null && serverReceives.enabled();
boolean balanced = true;
while(serializable.hasNext()) {
final ModelField field = serializable.next();
boolean addedClient = false;
if (toClientEnabled) {
// need read only, unless overridden
final ServerToClient server = field.getServerToClient();
if (server == null || server.enabled()) {
toClient.give(field);
addedClient = true;
}
}
if (toServerEnabled) {
// need write only, unless overridden.
final ClientToServer client = field.getClientToServer();
if (client == null || client.enabled()) {
toServer.give(field);
} else if (addedClient) {
balanced = false;
}
}
}
final ModelSerializerResult result =
writeClientSerializer(balanced, toClient.forEach(), toServer.forEach());
if (balanced) {
// A balanced serializer, by default, doesn't need a custom server serializer;
// it can just re-use the same serializer on client and server.
return result;
}
return writeServerSerializer(result, toClient.forEach(), toServer.forEach());
}
protected ModelSerializerResult writeClientSerializer(final boolean balanced,
final Iterable<ModelField> toClient, final Iterable<ModelField> toServer) {
final ModelSerializerResult result = new ModelSerializerResult();
return result;
}
protected ModelSerializerResult writeServerSerializer(final ModelSerializerResult result,
final Iterable<ModelField> toClient, final Iterable<ModelField> toServer) {
return result;
}
}