package algorithms;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import code.ast.CodeUtil;
import code.ast.CompilationUnit;
import code.ast.MethodDeclaration;
import code.ast.TypeDeclaration;
import code.ast.VariableDeclaration;
import models.Edge;
import models.Node;
import models.algebra.Expression;
import models.algebra.InvalidMessage;
import models.algebra.ParameterizedIdentifierIsFutureWork;
import models.algebra.Term;
import models.algebra.Type;
import models.algebra.UnificationFailed;
import models.algebra.ValueUndefined;
import models.dataConstraintModel.ChannelGenerator;
import models.dataConstraintModel.ChannelMember;
import models.dataConstraintModel.DataConstraintModel;
import models.dataFlowModel.DataFlowModel;
import models.dataFlowModel.DataflowChannelGenerator;
import models.dataFlowModel.PushPullAttribute;
import models.dataFlowModel.PushPullValue;
import models.dataFlowModel.ResolvingMultipleDefinitionIsFutureWork;
import models.dataFlowModel.ResourceDependency;
import models.dataFlowModel.ResourceDependencyGraph;
import models.dataFlowModel.ResourceNode;
import models.dataFlowModel.StoreAttribute;
public class JerseyMethodBodyGenerator {
private static String baseURL = "http://localhost:8080";
public static ArrayList<CompilationUnit> doGenerate(ResourceDependencyGraph graph, DataFlowModel model, ArrayList<CompilationUnit> codes) {
// Create a map from type names (lower case) to their types.
Map<String, TypeDeclaration> typeMap = new HashMap<>();
for (CompilationUnit code: codes) {
for (TypeDeclaration type: code.types()) {
typeMap.put(type.getTypeName().substring(0,1).toLowerCase() + type.getTypeName().substring(1), type);
}
}
// Generate the body of each update or getter method.
try {
Set<MethodDeclaration> chainedCalls = new HashSet<>();
for (Edge e: graph.getEdges()) {
ResourceDependency d = (ResourceDependency) e;
PushPullAttribute pushPull = (PushPullAttribute) d.getAttribute();
ResourceNode src = (ResourceNode) d.getSource();
ResourceNode dst = (ResourceNode) d.getDestination();
String srcResourceName = src.getIdentifierTemplate().getResourceName();
String dstResourceName = dst.getIdentifierTemplate().getResourceName();
TypeDeclaration srcType = typeMap.get(srcResourceName);
TypeDeclaration dstType = typeMap.get(dstResourceName);
for (ChannelMember out: d.getChannelGenerator().getOutputChannelMembers()) {
if (out.getIdentifierTemplate() == dst.getIdentifierTemplate()) {
if (pushPull.getOptions().get(0) == PushPullValue.PUSH && srcType != null) {
// for push data transfer
MethodDeclaration update = getUpdateMethod(dstType, srcType);
if (((StoreAttribute) dst.getAttribute()).isStored()) {
// update stored state of dst resource (when every incoming edge is in push style)
Expression updateExp = d.getChannelGenerator().deriveUpdateExpressionOf(out, JerseyCodeGenerator.pushAccessor);
String[] sideEffects = new String[] {""};
String curState = updateExp.toImplementation(sideEffects);
String updateStatement;
if (updateExp instanceof Term && ((Term) updateExp).getSymbol().isImplWithSideEffect()) {
updateStatement = sideEffects[0];
} else {
updateStatement = sideEffects[0] + dstResourceName + " = " + curState + ";";
}
if (update.getBody() == null || !update.getBody().getStatements().contains(updateStatement)) {
update.addFirstStatement(updateStatement);
}
}
if (dst.getIndegree() > 1) {
// update a cash of src resource (when incoming edges are multiple)
String cashStatement = "this." + srcResourceName + " = " + srcResourceName + ";";
if (update.getBody() == null || !update.getBody().getStatements().contains(cashStatement)) {
update.addFirstStatement(cashStatement);
}
}
// to convert a json param to a tuple object.
for (VariableDeclaration param: update.getParameters()) {
Type paramType = param.getType();
String paramName = param.getName();
String paramConverter = "";
if (DataConstraintModel.typeList.isAncestorOf(paramType) && paramType != DataConstraintModel.typeList) {
Type compType = TypeInference.getListComponentType(paramType);
if (DataConstraintModel.typeTuple.isAncestorOf(compType)) {
param.setType(DataConstraintModel.typeListStr);
param.setName(paramName + "_json");
paramConverter += paramType.getInterfaceTypeName() + " " + paramName + " = new " + paramType.getImplementationTypeName() + "();\n";
paramConverter += "for (String str: " + param.getName() + ") {\n";
String mapTypeName = convertFromEntryToMapType(compType);
paramConverter += "\t" + mapTypeName + " i = new ObjectMapper().readValue(str, HashMap.class);\n";
paramConverter += "\t" + paramName + ".add(" + getCodeForConversionFromMapToTuple(compType, "i") + ");\n";
paramConverter += "}";
update.addThrow("JsonProcessingException");
}
} else if (DataConstraintModel.typeTuple.isAncestorOf(paramType)) {
param.setType(DataConstraintModel.typeString);
param.setName(paramName + "_json");
paramConverter += "{\n";
String mapTypeName = convertFromEntryToMapType(paramType);
paramConverter += "\t" + mapTypeName + " i = new ObjectMapper().readValue(" + paramName + "_json" + ", HashMap.class);\n";
paramConverter += "\t" + paramName + " = " + getCodeForConversionFromMapToTuple(paramType, "i") + ";\n";
paramConverter += "}";
update.addThrow("JsonProcessingException");
}
if (paramConverter.length() > 0) update.addFirstStatement(paramConverter);
}
MethodDeclaration getter = getGetterMethod(dstType);
if (((StoreAttribute) dst.getAttribute()).isStored()) {
// returns the state stored in a field.
if (getter.getBody() == null || getter.getBody().getStatements().size() == 0) {
getter.addStatement("return " + dstResourceName + ";");
}
}
// src side (for a chain of update method invocations)
String httpMethod = null;
if (((StoreAttribute) dst.getAttribute()).isStored()) {
httpMethod = "post";
} else {
httpMethod = "put";
}
for (MethodDeclaration srcUpdate: getUpdateMethods(srcType)) {
if (srcUpdate != null) {
String srcResName = null;
if (dst.getIndegree() > 1) {
srcResName = srcResourceName;
}
if (!chainedCalls.contains(srcUpdate)) {
srcUpdate.addStatement(getHttpMethodParamsStatement(srcType.getTypeName(), src.getIdentifierTemplate().getResourceStateType(), srcResourceName));
srcUpdate.addStatement("String result = " + getHttpMethodCallStatement(baseURL, dstResourceName, srcResName, httpMethod));
chainedCalls.add(srcUpdate);
} else {
srcUpdate.addStatement("result = " + getHttpMethodCallStatement(baseURL, dstResourceName, srcResName, httpMethod));
}
srcUpdate.addThrow("JsonProcessingException");
}
}
MethodDeclaration srcInput = getInputMethod(srcType, src, model);
if (srcInput != null) {
String srcResName = null;
if (dst.getIndegree() > 1) {
srcResName = srcResourceName;
}
if (!chainedCalls.contains(srcInput)) {
srcInput.addStatement(getHttpMethodParamsStatement(srcType.getTypeName(), src.getIdentifierTemplate().getResourceStateType(), srcResourceName));
srcInput.addStatement("String result = " + getHttpMethodCallStatement(baseURL, dstResourceName, srcResName, httpMethod));
chainedCalls.add(srcInput);
} else {
srcInput.addStatement("result = " + getHttpMethodCallStatement(baseURL, dstResourceName, srcResName, httpMethod));
}
srcInput.addThrow("JsonProcessingException");
}
} else {
// for pull (or push/pull) data transfer
MethodDeclaration getter = getGetterMethod(dstType);
if (getter.getBody() == null || getter.getBody().getStatements().size() == 0) {
String[] sideEffects = new String[] {""};
String curState = d.getChannelGenerator().deriveUpdateExpressionOf(out, JerseyCodeGenerator.pullAccessor).toImplementation(sideEffects);
getter.addStatement(sideEffects[0] + "return " + curState + ";");
}
// get src side resource state by pull data transfer.
String varName = new String(srcResourceName);
Type srcResourceType = src.getIdentifierTemplate().getResourceStateType();
String respTypeName = srcResourceType.getInterfaceTypeName();
String respConverter = "";
if (DataConstraintModel.typeList.isAncestorOf(srcResourceType) && srcResourceType != DataConstraintModel.typeList) {
Type compType = TypeInference.getListComponentType(srcResourceType);
if (DataConstraintModel.typeTuple.isAncestorOf(compType)) {
varName += "_json";
String mapTypeName = convertFromEntryToMapType(compType);
respTypeName = "List<" + mapTypeName + ">";
respConverter += srcResourceType.getInterfaceTypeName() + " " + srcResourceName + " = new " + srcResourceType.getImplementationTypeName() + "();\n";
respConverter += "for (" + mapTypeName + " i: " + varName + ") {\n";
respConverter += "\t" + srcResourceName + ".add(" + getCodeForConversionFromMapToTuple(compType, "i") + ");\n";
respConverter += "}";
getter.addThrow("JsonProcessingException");
}
} else if (DataConstraintModel.typeTuple.isAncestorOf(srcResourceType)) {
varName += "_json";
respTypeName = convertFromEntryToMapType(srcResourceType);
respConverter += srcResourceName + " = " + getCodeForConversionFromMapToTuple(srcResourceType, varName) + ";";
getter.addThrow("JsonProcessingException");
}
if (respConverter.length() > 0) {
getter.addFirstStatement(respConverter);
}
getter.addFirstStatement(respTypeName + " " + varName + " = " + getHttpMethodCallStatementWithResponse(baseURL, srcResourceName, "get", srcResourceType.getImplementationTypeName()));
}
}
}
}
// for source nodes
for (Node n: graph.getNodes()) {
ResourceNode resource = (ResourceNode) n;
String resourceName = resource.getIdentifierTemplate().getResourceName();
TypeDeclaration type = typeMap.get(resourceName);
if (type != null) {
// getter method
MethodDeclaration getter = getGetterMethod(type);
if (getter.getBody() == null || getter.getBody().getStatements().size() == 0) {
getter.addStatement("return " + resource.getIdentifierTemplate().getResourceName() + ";");
}
// methods for input events
Map.Entry<DataflowChannelGenerator, ChannelMember> ioChannelAndMember = getIOChannelAndMember(resource, model);
if (ioChannelAndMember != null) {
ChannelMember out = ioChannelAndMember.getValue();
Term message = (Term) out.getStateTransition().getMessageExpression();
MethodDeclaration input = getMethod(type, message.getSymbol().getName());
if (input != null) {
Expression updateExp = ioChannelAndMember.getKey().deriveUpdateExpressionOf(out, JerseyCodeGenerator.pushAccessor);
String[] sideEffects = new String[] {""};
String newState = updateExp.toImplementation(sideEffects);
String updateStatement;
if (updateExp instanceof Term && ((Term) updateExp).getSymbol().isImplWithSideEffect()) {
updateStatement = sideEffects[0];
} else {
updateStatement = sideEffects[0] + "this." + resourceName + " = " + newState + ";";
}
if (input.getBody() == null || !input.getBody().getStatements().contains(updateStatement)) {
input.addFirstStatement(updateStatement);
}
}
}
}
}
} catch (ParameterizedIdentifierIsFutureWork | ResolvingMultipleDefinitionIsFutureWork
| InvalidMessage | UnificationFailed | ValueUndefined e1) {
e1.printStackTrace();
}
return codes;
}
private static String convertFromEntryToMapType(Type type) {
String mapTypeName = type.getInterfaceTypeName();
mapTypeName = mapTypeName.replace("Map.Entry", "Map");
for (int idx = mapTypeName.indexOf("<", 0); idx >= 0; idx = mapTypeName.indexOf("<", idx + 1)) {
int to = mapTypeName.indexOf(",", idx);
if (to > idx) {
mapTypeName = mapTypeName.substring(0, idx + 1) + "String" + mapTypeName.substring(to); // All elements except for the last one have the string type.
}
}
return mapTypeName;
}
private static String getCodeForConversionFromMapToTuple(Type tupleType, String mapVar) {
String decoded = "$x";
List<Type> elementsTypes = TypeInference.getTupleComponentTypes(tupleType);
String elementBase = mapVar;
for (Type elmType: elementsTypes.subList(0, elementsTypes.size() - 1)) {
elementBase += ".entrySet().iterator().next()";
if (elmType == DataConstraintModel.typeBoolean) {
decoded = decoded.replace("$x", "new AbstractMap.SimpleEntry<>(Boolean.parseBoolean(" + elementBase + ".getKey()), $x)");
} else if (elmType == DataConstraintModel.typeInt) {
decoded = decoded.replace("$x", "new AbstractMap.SimpleEntry<>(Integer.parseInt(" + elementBase + ".getKey()), $x)");
} else if (elmType == DataConstraintModel.typeLong) {
decoded = decoded.replace("$x", "new AbstractMap.SimpleEntry<>(Long.parseLong(" + elementBase + ".getKey()), $x)");
} else if (elmType == DataConstraintModel.typeFloat) {
decoded = decoded.replace("$x", "new AbstractMap.SimpleEntry<>(Float.parseFloat(" + elementBase + ".getKey()), $x)");
} else if (elmType == DataConstraintModel.typeDouble) {
decoded = decoded.replace("$x", "new AbstractMap.SimpleEntry<>(Double.parseDouble(" + elementBase + ".getKey()), $x)");
} else if (elmType == DataConstraintModel.typeString) {
decoded = decoded.replace("$x", "new AbstractMap.SimpleEntry<>(" + elementBase + ".getKey(), $x)");
} else {
// To do.
}
elementBase += ".getValue()";
}
decoded = decoded.replace("$x", elementBase);
return decoded;
}
private static String getHttpMethodParamsStatement(String callerResourceName, Type paramType, String paramName) {
if (DataConstraintModel.typeList.isAncestorOf(paramType)) {
Type compType = TypeInference.getListComponentType(paramType);
String statements = "Form form = new Form();\n";
String wrapperType = DataConstraintModel.getWrapperType(compType);
if (wrapperType == null) {
statements += "for (" + compType.getInterfaceTypeName() + " i: " + paramName + ") {\n";
} else {
statements += "for (" + wrapperType + " i: " + paramName + ") {\n";
}
if (DataConstraintModel.typeTuple.isAncestorOf(compType) || DataConstraintModel.typeList.isAncestorOf(compType)) {
statements += "\tform.param(\"" + paramName + "\", new ObjectMapper().writeValueAsString(i));\n";
} else {
statements += "\tform.param(\"" + paramName + "\", i.toString());\n";
}
statements += "}\n";
statements += "Entity<Form> entity = Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED);";
return statements;
// return "Entity<String> entity = Entity.entity(" + paramName + ".toString(), MediaType.APPLICATION_JSON);";
}
return "Entity<Form> entity = Entity.entity(new Form().param(\"" + paramName + "\", " + CodeUtil.getToStringExp(paramType.getImplementationTypeName(), paramName) + "), MediaType.APPLICATION_FORM_URLENCODED_TYPE);";
}
private static String getHttpMethodCallStatement(String baseURL, String resourceName, String srcResName, String httpMethod) {
if (srcResName == null) {
return "client.target(\"" + baseURL + "\").path(\"/" + resourceName + "\").request()." + httpMethod + "(entity, String.class);";
} else {
// For each source resource, a child resource is defined in the destination resource so that its state can be updated separately.
return "client.target(\"" + baseURL + "\").path(\"/" + resourceName + "/" + srcResName + "\").request()." + httpMethod + "(entity, String.class);";
}
}
private static String getHttpMethodCallStatementWithResponse(String baseURL, String resourceName, String httpMethod, String respImplName) {
String responseShortTypeName = respImplName;
if (respImplName.contains("<")) {
responseShortTypeName = respImplName.substring(0, respImplName.indexOf("<"));
}
return "client.target(\"" + baseURL + "\").path(\"/" + resourceName + "\").request()." + httpMethod + "(" + responseShortTypeName + ".class);";
}
private static MethodDeclaration getUpdateMethod(TypeDeclaration type, TypeDeclaration from) {
for (MethodDeclaration m: type.getMethods()) {
if (m.getName().equals("update" + from.getTypeName())) return m;
}
return null;
}
private static List<MethodDeclaration> getUpdateMethods(TypeDeclaration type) {
List<MethodDeclaration> updates = new ArrayList<>();
for (MethodDeclaration m: type.getMethods()) {
if (m.getName().startsWith("update")) {
updates.add(m);
}
}
return updates;
}
private static MethodDeclaration getGetterMethod(TypeDeclaration type) {
for (MethodDeclaration m: type.getMethods()) {
if (m.getName().startsWith("get")) return m;
}
return null;
}
private static Map.Entry<DataflowChannelGenerator, ChannelMember> getIOChannelAndMember(ResourceNode resource, DataFlowModel model) {
for (ChannelGenerator c: model.getIOChannelGenerators()) {
DataflowChannelGenerator channel = (DataflowChannelGenerator) c;
// I/O channel
for (ChannelMember out: channel.getOutputChannelMembers()) {
if (out.getIdentifierTemplate().equals(resource.getIdentifierTemplate())) {
if (out.getStateTransition().getMessageExpression() instanceof Term) {
// not an identity element
return new AbstractMap.SimpleEntry<>(channel, out);
}
}
}
}
return null;
}
private static MethodDeclaration getInputMethod(TypeDeclaration type, ResourceNode resource, DataFlowModel model) {
for (ChannelGenerator c: model.getIOChannelGenerators()) {
DataflowChannelGenerator channel = (DataflowChannelGenerator) c;
// I/O channel
for (ChannelMember out: channel.getOutputChannelMembers()) {
if (out.getIdentifierTemplate().equals(resource.getIdentifierTemplate())) {
if (out.getStateTransition().getMessageExpression() instanceof Term) {
// not an identity element
Term message = (Term) out.getStateTransition().getMessageExpression();
MethodDeclaration input = getMethod(type, message.getSymbol().getName());
return input;
}
}
}
}
return null;
}
private static MethodDeclaration getMethod(TypeDeclaration type, String methodName) {
for (MethodDeclaration m: type.getMethods()) {
if (m.getName().equals(methodName)) return m;
}
return null;
}
}