package core.framework.impl.mongo;
import core.framework.api.mongo.Id;
import core.framework.api.util.Lists;
import core.framework.api.util.Sets;
import core.framework.api.util.Strings;
import core.framework.impl.code.CodeBuilder;
import core.framework.impl.code.DynamicInstanceBuilder;
import core.framework.impl.reflect.Classes;
import core.framework.impl.reflect.GenericTypes;
import org.bson.types.ObjectId;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author neo
*/
final class EntityEncoderBuilder<T> {
final Map<String, String> methods = new LinkedHashMap<>();
final Set<Class<? extends Enum<?>>> enumClasses = Sets.newHashSet();
final List<String> fields = Lists.newArrayList();
private final Class<T> entityClass;
private final String helper = EntityCodecHelper.class.getCanonicalName();
EntityEncoderBuilder(Class<T> entityClass) {
this.entityClass = entityClass;
}
public EntityEncoder<T> build() {
DynamicInstanceBuilder<EntityEncoder<T>> builder = new DynamicInstanceBuilder<>(EntityEncoder.class, EntityEncoder.class.getCanonicalName() + "$" + entityClass.getSimpleName());
buildMethods();
fields.forEach(builder::addField);
methods.values().forEach(builder::addMethod);
return builder.build();
}
private void buildMethods() {
String methodName = encodeEntityMethod(entityClass);
CodeBuilder builder = new CodeBuilder().append("public void encode(org.bson.BsonWriter writer, Object entity) {\n")
.indent(1).append("{}(writer, ({}) entity);\n", methodName, entityClass.getCanonicalName())
.append("}");
methods.put("encode", builder.build());
}
private String encodeEntityMethod(Class<?> entityClass) {
String entityClassName = entityClass.getCanonicalName();
String methodName = "encode_" + entityClassName.replaceAll("\\.", "_");
if (methods.containsKey(methodName)) return methodName;
CodeBuilder builder = new CodeBuilder().append("private void {}(org.bson.BsonWriter writer, {} entity) {\n", methodName, entityClassName);
builder.indent(1).append("writer.writeStartDocument();\n");
for (Field field : Classes.instanceFields(entityClass)) {
Type fieldType = field.getGenericType();
String fieldVariable = "entity." + field.getName();
String mongoFieldName;
if (field.isAnnotationPresent(Id.class)) mongoFieldName = "_id";
else mongoFieldName = field.getDeclaredAnnotation(core.framework.api.mongo.Field.class).name();
builder.indent(1).append("writer.writeName(\"{}\");\n", mongoFieldName);
encodeField(builder, fieldVariable, fieldType, 1);
}
builder.indent(1).append("writer.writeEndDocument();\n");
builder.append("}\n");
methods.put(methodName, builder.build());
return methodName;
}
private String encodeListMethod(Class<?> valueClass) {
String valueClassName = valueClass.getCanonicalName();
String methodName = ("encode_" + List.class.getCanonicalName() + "_" + valueClassName).replaceAll("\\.", "_");
if (methods.containsKey(methodName)) return methodName;
CodeBuilder builder = new CodeBuilder();
builder.append("private void {}(org.bson.BsonWriter writer, java.util.List list) {\n", methodName);
builder.indent(1).append("writer.writeStartArray();\n")
.indent(1).append("for (java.util.Iterator iterator = list.iterator(); iterator.hasNext(); ) {\n")
.indent(2).append("{} value = ({}) iterator.next();\n", valueClassName, valueClassName);
encodeField(builder, "value", valueClass, 2);
builder.indent(1).append("}\n")
.indent(1).append("writer.writeEndArray();\n")
.append("}\n");
methods.put(methodName, builder.build());
return methodName;
}
private String encodeMapMethod(Class<?> valueClass) {
String valueClassName = valueClass.getCanonicalName();
String methodName = ("encode_" + Map.class.getCanonicalName() + "_" + valueClassName).replaceAll("\\.", "_");
if (methods.containsKey(methodName)) return methodName;
CodeBuilder builder = new CodeBuilder();
builder.append("private void {}(org.bson.BsonWriter writer, java.util.Map map) {\n", methodName);
builder.indent(1).append("writer.writeStartDocument();\n")
.indent(1).append("for (java.util.Iterator iterator = map.entrySet().iterator(); iterator.hasNext(); ) {\n")
.indent(2).append("java.util.Map.Entry entry = (java.util.Map.Entry) iterator.next();\n")
.indent(2).append("String key = (String) entry.getKey();\n")
.indent(2).append("{} value = ({}) entry.getValue();\n", valueClassName, valueClassName)
.indent(2).append("writer.writeName(key);\n");
encodeField(builder, "value", valueClass, 2);
builder.indent(1).append("}\n")
.indent(1).append("writer.writeEndDocument();\n")
.append("}\n");
methods.put(methodName, builder.build());
return methodName;
}
private void encodeField(CodeBuilder builder, String fieldVariable, Type fieldType, int indent) {
Class<?> fieldClass = GenericTypes.rawClass(fieldType);
if (String.class.equals(fieldClass)) {
builder.indent(indent).append("{}.writeString(writer, {});\n", helper, fieldVariable);
} else if (Integer.class.equals(fieldClass)) {
builder.indent(indent).append("{}.writeInteger(writer, {});\n", helper, fieldVariable);
} else if (Long.class.equals(fieldClass)) {
builder.indent(indent).append("{}.writeLong(writer, {});\n", helper, fieldVariable);
} else if (LocalDateTime.class.equals(fieldClass)) {
builder.indent(indent).append("{}.writeLocalDateTime(writer, {});\n", helper, fieldVariable);
} else if (ZonedDateTime.class.equals(fieldClass)) {
builder.indent(indent).append("{}.writeZonedDateTime(writer, {});\n", helper, fieldVariable);
} else if (fieldClass.isEnum()) {
String enumCodecVariable = registerEnumCodec(fieldClass);
builder.indent(indent).append("{}.encode(writer, {}, null);\n", enumCodecVariable, fieldVariable);
} else if (Double.class.equals(fieldClass)) {
builder.indent(indent).append("{}.writeDouble(writer, {});\n", helper, fieldVariable);
} else if (Boolean.class.equals(fieldClass)) {
builder.indent(indent).append("{}.writeBoolean(writer, {});\n", helper, fieldVariable);
} else if (ObjectId.class.equals(fieldClass)) {
builder.indent(indent).append("{}.writeObjectId(writer, {});\n", helper, fieldVariable);
} else if (GenericTypes.isGenericList(fieldType)) {
String methodName = encodeListMethod(GenericTypes.listValueClass(fieldType));
builder.indent(indent).append("if ({} == null) writer.writeNull();\n", fieldVariable);
builder.indent(indent).append("else {}(writer, {});\n", methodName, fieldVariable);
} else if (GenericTypes.isGenericStringMap(fieldType)) {
String methodName = encodeMapMethod(GenericTypes.mapValueClass(fieldType));
builder.indent(indent).append("if ({} == null) writer.writeNull();\n", fieldVariable);
builder.indent(indent).append("else {}(writer, {});\n", methodName, fieldVariable);
} else {
String encodeFieldMethod = encodeEntityMethod(fieldClass);
builder.indent(indent).append("if ({} == null) writer.writeNull();\n", fieldVariable);
builder.indent(indent).append("else {}(writer, {});\n", encodeFieldMethod, fieldVariable);
}
}
private String registerEnumCodec(Class<?> fieldClass) {
@SuppressWarnings("unchecked")
boolean added = enumClasses.add((Class<? extends Enum<?>>) fieldClass);
String fieldVariable = fieldClass.getCanonicalName().replaceAll("\\.", "_") + "Codec";
if (added) {
String field = Strings.format("private final {} {} = new {}({}.class);\n",
EnumCodec.class.getCanonicalName(),
fieldVariable,
EnumCodec.class.getCanonicalName(),
fieldClass.getCanonicalName());
fields.add(field);
}
return fieldVariable;
}
}