package xapi.dev.ui; import com.github.javaparser.ast.Node; import com.github.javaparser.ast.expr.AnnotationExpr; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.FieldAccessExpr; import com.github.javaparser.ast.expr.JsonContainerExpr; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.UiAttrExpr; import com.github.javaparser.ast.expr.UnaryExpr; import com.github.javaparser.ast.plugin.NodeTransformer; import com.github.javaparser.ast.plugin.Transformer; import xapi.dev.source.ClassBuffer; import xapi.dev.source.PrintBuffer; import xapi.fu.In1Out1; import xapi.fu.In2Out1; import xapi.fu.Lazy; import xapi.source.api.IsType; import java.util.Optional; /** * Created by James X. Nelson (james @wetheinter.net) on 6/19/16. */ public class DataFeatureGenerator extends UiFeatureGenerator { @Override public UiVisitScope startVisit( UiGeneratorTools service, UiComponentGenerator generator, ContainerMetadata container, UiAttrExpr attr ) { final Expression value = attr.getExpression(); if (value instanceof JsonContainerExpr) { // Check the annotation for a type to use, // either a bean with all the keys that match the supplied data, // or a list / map / array / container type to use. JsonContainerExpr json = (JsonContainerExpr) value; Optional<AnnotationExpr> anno = attr.getAnnotation( a -> a.getName().getName().equalsIgnoreCase("type")); DataTypeOptions opts; final ClassBuffer cb = container.getSourceBuilder().getClassBuffer(); if (anno.isPresent()) { opts = getDatatypeFrom(cb, container, json, anno.get()); } else { opts = getDatatypeFrom(cb, container, json); } String var = printFactory(json, container, anno, opts, cb, generator.getTransformer()); // register `varName.out1()` as a replacement for all accessors of this particular data MethodCallExpr expr = new MethodCallExpr(new NameExpr(var), "out1"); container.registerFieldProvider(container.getRefName(), "data", newTransformer(service, generator, container, json, opts, expr)); return UiVisitScope.DEFAULT_CONTAINER; } else { throw new IllegalArgumentException("Cannot assign a node of type " + value.getClass() + " to a data feature; bad data: " + value); } } protected static class DataTypeTransformer extends NodeTransformer { private final DataTypeOptions opts; private final JsonContainerExpr json; public DataTypeTransformer( DataTypeOptions opts, JsonContainerExpr json, Node newNode, ClassBuffer out, In2Out1<Node, Expression, Node> createRead, In2Out1<Node, Expression, Node> createWrite, In1Out1<String, String> addImport ) { super(newNode, out, createRead, createWrite, addImport); this.opts = opts; this.json = json; } public DataTypeTransformer( DataTypeOptions opts, JsonContainerExpr json, Node newNode, ClassBuffer out, In2Out1<Node, Expression, Node> createRead, In2Out1<Node, Expression, Node> createWrite, In2Out1<Node, Expression, Node> compute, In1Out1<String, String> addImport ) { super(newNode, out, createRead, createWrite, compute, addImport); this.opts = opts; this.json = json; } @Override protected String getDataType(String keyName) { final IsType type = opts.getFieldTypes().get(keyName); if (type == null) { if (opts.getType() != null) { return opts.getType(); } } return type == null ? super.getDataType(keyName) : type.getQualifiedName(); } @Override public Node transformUnary(Expression source, UnaryExpr expr) { final Node node = getNode(); final Expression scope = expr.getExpr(); if (scope instanceof FieldAccessExpr) { FieldAccessExpr field = (FieldAccessExpr) scope; if (field.getScope() == node) { // an expression like $root.data.key ++ // was transformed into rootData.out1().key ++ // and the scope is rootData.out1() // so now we can look at "key" to see it's datatype, // and figure out how to compute the given operation. String key = field.getField(); final IsType type = opts.getFieldTypes().get(key); switch (expr.getOperator()) { case posDecrement: // key-- case posIncrement: // key++ // we want to return the current value, // but store a mutated value // For StringTo types we can use computeReturnPrevious // createRead.io() case preDecrement: // --key case preIncrement: // ++key // For StringTo types we can use compute case inverse: // !key case negative: // -key case not: // !key case positive: // +key } } } return super.transformUnary(source, expr); } } protected NodeTransformer newTransformer( UiGeneratorTools service, UiComponentGenerator generator, ContainerMetadata container, JsonContainerExpr value, DataTypeOptions opts, Node expr ) { final In2Out1<Node, Expression, Node> read = (i, k)->i; final In2Out1<Node, Expression, Node> write = (i, k)->i; switch (opts.getType()) { case "xapi.collect.api.StringTo": break; case "xapi.collect.api.IntTo": break; default: if (value.isArray()) { // modifiers will be .get() and .set() } else { // map // modifiers will be .get() and .put() } } return new DataTypeTransformer(opts, value, expr, container.getSourceBuilder().getClassBuffer(), read, write, container.getSourceBuilder()::addImport); } protected String printFactory( JsonContainerExpr json, ContainerMetadata container, Optional<AnnotationExpr> anno, DataTypeOptions opts, ClassBuffer cb, Transformer transformer ) { String collection = cb.addImport(opts.getCollection()); String type = opts.getType(); boolean hasTwoGenerics = type.indexOf(',') != -1; if (hasTwoGenerics) { type = type.replaceFirst("String\\s*,\\s*", ""); } type = type.indexOf('.') == -1 ? opts.getType() : cb.addImport(opts.getType()); String lazy = cb.addImport(Lazy.class); String varName = container.newVarName(container.getRefName()+"Data"); String fieldType = collection + "<" + (hasTwoGenerics ? "String, " : "") + type + ">"; final PrintBuffer initializer = cb.createField( lazy + "<" + fieldType + ">", varName ).getInitializer() .print(lazy).print(".deferred1(()->") ; String inst = opts.getFactory().replaceAll("[$]type", type); if (json.isEmpty()) { initializer.print(inst); } else { initializer.println("{"); initializer.indent(); initializer.println(fieldType + " data = " + inst + ";"); json.getPairs().forEach(pair->{ initializer.print("data." + opts.getAdder() + "("); if (json.isArray()) { initializer.println(pair.getValueExpr().toSource(transformer) + ");"); } else { initializer .print(pair.getKeyQuoted()) .print(", ") .print(pair.getValueExpr().toSource(transformer)) .println(");"); } }); initializer.println("return data;") .outdent() .print("}"); } initializer.println(");"); return varName; } protected DataTypeOptions dataTypeOptions() { return new DataTypeOptions(); } protected DataTypeOptions getDatatypeFrom(ClassBuffer cb, ContainerMetadata metadata, JsonContainerExpr json) { return dataTypeOptions().fromAnnotation(cb, json, null, metadata.isSearchTypes()); } protected DataTypeOptions getDatatypeFrom(ClassBuffer cb, ContainerMetadata metadata, JsonContainerExpr json, AnnotationExpr anno) { return dataTypeOptions().fromAnnotation(cb, json, anno, metadata.isSearchTypes()); } }