package com.github.javaparser.ast.plugin;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.body.VariableDeclaratorId;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.expr.UnaryExpr.Operator;
import com.github.javaparser.ast.stmt.ExpressionStmt;
import com.github.javaparser.ast.stmt.Statement;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.ast.type.UnknownType;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import xapi.collect.api.StringTo;
import xapi.dev.source.ClassBuffer;
import xapi.dev.source.MethodBuffer;
import xapi.dev.source.PrintBuffer;
import xapi.except.NotImplemented;
import xapi.fu.In1Out1;
import xapi.fu.In2Out1;
import xapi.fu.Lazy;
import xapi.fu.Notifier;
import xapi.fu.Out1;
import xapi.source.X_Source;
import static xapi.collect.X_Collect.newStringMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Created by James X. Nelson (james @wetheinter.net) on 6/20/16.
*/
public class NodeTransformer {
protected static class NodeGeneratorContext<Ctx extends NodeGeneratorContext<Ctx>> {
Out1<ClassBuffer> source;
NodeGeneratorContext(Out1<ClassBuffer> source) {
this.source = source;
}
public String addImport(String importName) {
return source.out1().addImport(importName);
}
public String addImportStatic(String cls, String staticName) {
return source.out1().addImportStatic(cls, staticName);
}
protected final Ctx self() {
return (Ctx) this;
}
}
protected interface NodeGenerator<Ctx extends NodeGeneratorContext<Ctx>> {
Node generateNode(Ctx ctx, Node originalNode, Node currentNode);
}
private static final String KEY_TRANSFORMER = "__tr@nsf0rmer__"; // purposely misspelled to avoid collisions
private static final String KEY_SOURCE = "__s0urc3__"; // purposely misspelled to avoid collisions
public static boolean hasTransformer(Node n) {
return n.getExtra(KEY_TRANSFORMER) != null;
}
public static NodeTransformer getTransformer(Node n) {
return n.getExtra(KEY_TRANSFORMER);
}
public static List<String> findTransformers(Node n) {
final List<String> list = new ArrayList<>();
n.accept(new VoidVisitorAdapter<Object>() {
@Override
public void visit(MethodCallExpr n, Object arg) {
final NodeTransformer transformer = getTransformer(n);
if (transformer != null) {
final Expression source = n.getExtra(KEY_SOURCE);
list.add(transformer.generateTransformer(source));
}
super.visit(n, arg);
}
}, null);
return list;
}
private final Node newNode;
protected final In2Out1<Node, Expression, Node> createRead;
protected final In2Out1<Node, Expression, Node> createWrite;
protected final In1Out1<String, String> addImport;
protected final In2Out1<Node, Expression, Node> compute;
protected final ClassBuffer out;
private final StringTo<String> computeNames;
private final StringTo<String> getterNames;
private final StringTo<String> setterNames;
private final StringTo<String> notifierNames;
private final StringTo<PrintBuffer> notificationSites;
public NodeTransformer(
Node newNode,
ClassBuffer out,
In2Out1<Node, Expression, Node> createRead,
In2Out1<Node, Expression, Node> createWrite,
In1Out1<String, String> addImport
) {
this(newNode, out, createRead, createWrite, createCompute(newNode, createRead, createWrite, addImport), addImport);
}
private static In2Out1<Node, Expression, Node> createCompute(
Node newNode,
In2Out1<Node, Expression, Node> createRead,
In2Out1<Node, Expression, Node> createWrite,
In1Out1<String, String> addImport
) {
return (n, e) -> {
String in2out1 = addImport.io(In2Out1.class.getName());
MethodCallExpr createCompute = new MethodCallExpr(new NameExpr(in2out1), "computeKeyValueTransform");
List<Expression> args = new ArrayList<>();
args.add((Expression) n);
args.add(e);
MethodCallExpr compute = new MethodCallExpr(createCompute, "io", args);
// method.setArgs();
// Map<String, String> m = new HashMap<>();
// In3Out1<Map<String, String>, String, In1Out1<String, String>, String> f =
// (map, k, io) -> {
// String old = map.get(k);
// String computed = io.io(old);
// map.put(k, computed);
// return computed;
// };
//
// // In3Out1<Map<String, String>, String, In1Out1<String, String>, String>
// // computer =
// In2Out1.computeKeyValueTransform(m, Map::get, Map::put)
// .io("s", (k, v)->v == null ? "" : v+1);
return null;
};
}
public NodeTransformer(
Node newNode,
ClassBuffer out,
In2Out1<Node, Expression, Node> createRead,
In2Out1<Node, Expression, Node> createWrite,
In2Out1<Node, Expression, Node> compute,
In1Out1<String, String> addImport
) {
this.newNode = newNode;
this.createRead = createRead;
this.createWrite = createWrite;
this.compute = compute;
this.addImport = addImport;
this.out = out;
computeNames = newStringMap(String.class);
getterNames = newStringMap(String.class);
setterNames = newStringMap(String.class);
notifierNames = newStringMap(String.class);
notificationSites = newStringMap(MethodBuffer.class);
}
public Node transformBinary(Expression source, BinaryExpr expr) {
if (expr.getLeft() instanceof FieldAccessExpr) {
FieldAccessExpr field = (FieldAccessExpr) expr.getLeft();
if (field.getScope() == newNode) {
String get = generateGetterMethod(field);
MethodCallExpr invoke = new MethodCallExpr();
invoke.setName(get);
expr.setLeft(invoke);
initExtras(invoke, field);
}
}
if (expr.getRight() instanceof FieldAccessExpr) {
FieldAccessExpr field = (FieldAccessExpr) expr.getRight();
if (field.getScope() == newNode) {
String get = generateGetterMethod(field);
MethodCallExpr invoke = new MethodCallExpr();
invoke.setName(get);
expr.setRight(invoke);
initExtras(invoke, field);
}
}
return expr;
}
public Node transformUnary(Expression source, UnaryExpr expr) {
final Expression data = expr.getExpr();
switch (expr.getOperator()) {
// x++
case posIncrement:
// x--
case posDecrement:
// For post increment / decrement,
// we want to store the de/incremented value,
// but use the current value.
// So, we will generate setX(getX() + 1),
// as the setX function returns the previous value
final String setter = generateSetterMethod(data);
final String getter = generateGetterMethod(data);
MethodCallExpr methodCall = new MethodCallExpr();
methodCall.setName(setter);
MethodCallExpr get = new MethodCallExpr();
get.setName(getter);
final BinaryExpr.Operator operator = expr.getOperator() == Operator.posIncrement ? BinaryExpr.Operator.plus : BinaryExpr.Operator.minus;
BinaryExpr op = new BinaryExpr(get, new IntegerLiteralExpr("1"), operator);
methodCall.setArgs(Arrays.asList(op));
initExtras(methodCall, data);
return methodCall;
// ++x
// --x
case preIncrement:
case preDecrement:
// For pre increment / decrement,
// we want to de/increment the value,
// and use the updated value immediately
// So, we will use computeX_1(i -> i++)
// TODO: Have a global INCREMENT function we can just reference instead
String compute = generateComputeMethod(data) + "_1";
List<Parameter> params = new ArrayList<>();
Type type = new UnknownType();
VariableDeclaratorId id = new VariableDeclaratorId("i");
params.add(new Parameter(type, id));
UnaryExpr copy = new UnaryExpr(new NameExpr("i"), expr.getOperator());
Statement body = new ExpressionStmt(copy);
LambdaExpr lambda = new LambdaExpr(params, body, false);
methodCall = new MethodCallExpr();
methodCall.setName(compute);
methodCall.setArgs(Arrays.asList(lambda));
initExtras(methodCall, data);
return methodCall;
case inverse:
case negative:
case not:
case positive:
throw new NotImplemented("Need to implement unary operator " + expr.getOperator() + " in " + getClass());
}
return expr;
// return new StringLiteralExpr(expr.toSource());
}
// If you change the MethodCallExpr parameter to a weaker type,
// do be sure to update the visitor used in the findNotifiers method to match!
protected void initExtras(MethodCallExpr node, Expression data) {
node.addExtra(KEY_TRANSFORMER, this);
node.addExtra(KEY_SOURCE, data);
}
protected String generateComputeMethod(Expression expr) {
String computeName;
if (expr instanceof FieldAccessExpr) {
final FieldAccessExpr field = (FieldAccessExpr) expr;
if (field.getScope() == newNode) {
computeName = computeNames.get(field.getField());
if (computeName == null) {
String keyName = X_Source.toCamelCase(field.getField());
String datatype = out.addImport(getDataType(field.getField()));
datatype = X_Source.primitiveToObject(datatype);
String in1out1 = out.addImport(In1Out1.class);
boolean isMapType = isMapType(expr);
computeName = "compute" + keyName;
String getter = generateGetterMethod(expr);
String setter = generateSetterMethod(expr);
out.createMethod("public " + datatype + " " + computeName + "_1")
.addParameter(in1out1 + "<" + datatype + ", " + datatype + ">", "io")
.println(datatype + " original = " + getter + "();")
.println(datatype + " computed = io.io(original);")
.println(setter + "(computed);")
.returnValue("computed");
if (isMapType) {
String in2out1 = out.addImport(In2Out1.class);
String keyType = getKeyType(expr);
String escaped = escapedKey(keyType, field.getField());
out.createMethod("public " + datatype + " " + computeName + "_2")
.addParameter(in2out1 + "<" + keyType + ", " + datatype + ", " + datatype + ">", "io")
.println(datatype + " original = " + getter + "();")
.println(datatype + " computed = io.io( " + escaped + ", original);")
.println(setter + "(computed);")
.returnValue("computed");
}
computeNames.put(field.getField(), computeName);
}
return computeName;
}
}
throw new NotImplemented("Unable to extract a compute method for expression " + expr);
}
protected String generateTransformer(Expression expr) {
String notifierName;
if (expr instanceof FieldAccessExpr) {
final String fieldName = getFieldName(expr);
notifierName = notifierNames.get(fieldName);
if (notifierName == null) {
String datatype = out.addImport(getDataType(fieldName));
datatype = X_Source.primitiveToObject(datatype);
generateSetterMethod(expr); // will always need a setter inited to ensure notifier has somewhere to act
notifierName = "notify" + X_Source.toCamelCase(fieldName);
notifierNames.put(fieldName, notifierName);
String lazy = out.addImport(Lazy.class);
String notifier = out.addImport(Notifier.class);
out.createField(lazy+"<" + notifier + "<" + datatype + ">>", notifierName)
.setInitializer(lazy+".deferred1(" + notifier +"::new);");
// Add a hook in the setter method to trigger notifications.
final PrintBuffer notificationSite = notificationSites.get(fieldName);
if (notificationSite.isEmpty()) {
boolean ifGaurd = isOnlyNotifyChanges(expr);
if (ifGaurd) {
String objects = out.addImport(Objects.class);
notificationSite.println("if (!" + objects +".equals(oldVal, val)) {");
notificationSite.indent();
}
notificationSite.println(notifierName+".out1().notifyListeners(oldVal, val);");
if (ifGaurd) {
notificationSite.outdent();
notificationSite.println("}");
}
}
}
return notifierName;
}
throw new NotImplemented("Unable to generate a transformer for expression " + expr);
}
protected boolean isOnlyNotifyChanges(Expression expr) {
return true;
}
protected String generateGetterMethod(Expression expr) {
String getterName;
if (expr instanceof FieldAccessExpr) {
final FieldAccessExpr field = (FieldAccessExpr) expr;
getterName = getterNames.get(field.getField());
if (getterName == null) {
if (field.getScope() == newNode) {
String keyName = X_Source.toCamelCase(field.getField());
String datatype = out.addImport(getDataType(field.getField()));
datatype = X_Source.primitiveToObject(datatype);
getterName = "get" + keyName;
final MethodBuffer getter = out.createMethod("public " + datatype + " " + getterName);
String get = nodeGetMethod(expr);
String keyType = getKeyType(expr);
String escapeKey = escapedKey(keyType, field.getField());
boolean cast = needsCast(expr, field.getField(), datatype);
getter.returnValue((cast ? "(" + datatype + ")" : "") + nodeSource() + "." + get + "(" + escapeKey + ")");
} else {
throw new NotImplemented("Unable to extract a getter method for expression " + expr);
}
getterNames.put(field.getField(), getterName);
}
return getterName;
}
throw new NotImplemented("Unable to extract a getter method for expression " + expr);
}
protected String generateSetterMethod(Expression expr) {
String setterName;
if (expr instanceof FieldAccessExpr) {
final FieldAccessExpr field = (FieldAccessExpr) expr;
setterName = setterNames.get(field.getField());
if (setterName == null) {
if (field.getScope() == newNode) {
String keyName = X_Source.toCamelCase(field.getField());
String datatype = out.addImport(getDataType(field.getField()));
datatype = X_Source.primitiveToObject(datatype);
setterName = "set" + keyName;
final MethodBuffer setter = out.createMethod("public " + datatype + " " + setterName)
.addParameter(datatype, "val");
String set = nodeSetMethod(expr);
String keyType = getKeyType(expr);
String escapeKey = escapedKey(keyType, field.getField());
setter.print(datatype + " oldVal = ");
if (needsCast(expr, field.getField(), datatype)) {
setter.print("(" + datatype + ")");
}
setter.println(nodeSource() + "." + set + "(" + escapeKey + ", val);");
// Add a printbuffer that we can optionally add a notifier for
final PrintBuffer buffer = new PrintBuffer();
setter.addToEnd(buffer);
buffer.setIndent(setter.getIndent());
notificationSites.put(field.getField(), buffer);
setter.returnValue("oldVal");
} else {
throw new NotImplemented("Unable to extract a setter method for expression " + expr);
}
setterNames.put(field.getField(), setterName);
}
return setterName;
}
throw new NotImplemented("Unable to extract a setter method for expression " + expr);
}
protected String getFieldName(Expression expr) {
if (expr instanceof FieldAccessExpr) {
final FieldAccessExpr field = (FieldAccessExpr) expr;
if (field.getScope() == newNode) {
return field.getField();
}
}
throw new NotImplemented("Unable to extract a field name for expression " + expr);
}
protected boolean needsCast(Expression expr, String field, String datatype) {
return !"Object".equals(datatype);
}
protected String escapedKey(String keyType, String field) {
switch (keyType) {
case "String":
case "java.lang.String":
return "\"" + X_Source.escape(field) + "\"";
case "int":
case "Integer":
case "java.lang.Integer":
return field;
default:
throw new IllegalArgumentException("Unknown keyType " + keyType);
}
}
protected String nodeGetMethod(Expression expr) {
return "get";
}
protected String nodeSetMethod(Expression expr) {
return isMapType(expr) ? "put" : "set";
}
protected String nodeSource() {
return newNode.toSource();
}
protected String getKeyType(Expression expr) {
return isMapType(expr) ? "String" : "int";
}
protected boolean isMapType(Expression expr) {
return expr instanceof FieldAccessExpr;
}
protected String getDataType(String keyName) {
return "Object";
}
public Node transformAssignExpr(AssignExpr expr) {
return expr;
}
public Node transformArrayAccess(ArrayAccessExpr expr) {
return expr;
}
public Node getNode() {
return newNode;
}
}