Newer
Older
AlgebraicDataflowArchitectureModel / AlgebraicDataflowArchitectureModel / src / algorithms / JerseyMethodBodyGenerator.java
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 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);
								}								
							}
							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()).isNeeded()) {
								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));
									}
								}
							}
							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));
								}
							}
						} 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 + ";");
							}
							getter.addFirstStatement(getHttpMethodCallStatement(baseURL, srcResourceName, "get", src.getIdentifierTemplate().getResourceStateType()));
						} 
					}
				}
			}
			// 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 getHttpMethodParamsStatement(String callerResourceName, Type paramType, String paramName) {
		if (DataConstraintModel.typeList.isAncestorOf(paramType)) {
			String compType = "Integer";
			String interfaceType = paramType.getInterfaceTypeName();
			if (interfaceType.contains("<")) {
				compType = interfaceType.substring(interfaceType.indexOf("<") + 1, interfaceType.lastIndexOf(">"));
			}						
			String statements = "Form form = new Form();\n";
			statements += "for (" + compType + " i: " + paramName + ") {\n";
			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 getHttpMethodCallStatement(String baseURL, String resourceName, String httpMethod, Type responseType) {
		String responseTypeName = responseType.getImplementationTypeName();
		String responseShortTypeName = responseTypeName;
		if (responseTypeName.contains("<")) {
			responseShortTypeName = responseTypeName.substring(0, responseTypeName.indexOf("<"));
		}
		return responseType.getInterfaceTypeName() + " " + resourceName + " = 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;
	}
}