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; } }