Newer
Older
AlgebraicDataflowArchitectureModel / AlgebraicDataflowArchitectureModel / src / generators / JerseyMethodBodyGenerator.java
package generators;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import algorithms.TypeInference;
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.algebra.Constant;
import models.algebra.Expression;
import models.algebra.Field;
import models.algebra.InvalidMessage;
import models.algebra.ParameterizedIdentifierIsFutureWork;
import models.algebra.Position;
import models.algebra.Symbol;
import models.algebra.Term;
import models.algebra.Type;
import models.algebra.UnificationFailed;
import models.algebra.ValueUndefined;
import models.algebra.Variable;
import models.dataConstraintModel.Channel;
import models.dataConstraintModel.ChannelMember;
import models.dataConstraintModel.DataConstraintModel;
import models.dataConstraintModel.JsonAccessor;
import models.dataConstraintModel.ResourceHierarchy;
import models.dataConstraintModel.ResourcePath;
import models.dataFlowModel.DataTransferModel;
import models.dataFlowModel.DataTransferChannel;
import models.dataFlowModel.PushPullAttribute;
import models.dataFlowModel.PushPullValue;
import models.dataFlowModel.ResolvingMultipleDefinitionIsFutureWork;
import models.dataFlowModel.ChannelNode;
import models.dataFlowModel.DataFlowEdge;
import models.dataFlowModel.DataFlowGraph;
import models.dataFlowModel.ResourceNode;
import models.dataFlowModel.StoreAttribute;
import models.dataFlowModel.DataTransferChannel.IResourceStateAccessor;

public class JerseyMethodBodyGenerator {
	private static String baseURL = "http://localhost:8080";

	public static ArrayList<CompilationUnit> doGenerate(DataFlowGraph graph, DataTransferModel model, ArrayList<CompilationUnit> codes) {
		// Create a map from type names (lower case) to their types.
		Map<String, TypeDeclaration> componentMap = new HashMap<>();
		for (CompilationUnit code: codes) {
			for (TypeDeclaration component: code.types()) {
				componentMap.put(component.getTypeName(), component);
			}
		}
		
		// Generate the body of each update or getter method.
		try {
			Set<MethodDeclaration> chainedCalls = new HashSet<>();
			Map<MethodDeclaration, Set<ResourcePath>> referredResources = new HashMap<>(); 
			for (Edge e: graph.getEdges()) {
				DataFlowEdge resToCh = (DataFlowEdge) e;
				if (!resToCh.isChannelToResource()) {
					PushPullAttribute pushPull = (PushPullAttribute) resToCh.getAttribute();
					ResourceNode src = (ResourceNode) resToCh.getSource();
					for (Edge chToRes: resToCh.getDestination().getOutEdges()) {
						ResourceNode dst = (ResourceNode) chToRes.getDestination();
						String srcResourceName = JerseyCodeGenerator.getComponentName(src.getResourceHierarchy());
						String dstResourceName = JerseyCodeGenerator.getComponentName(dst.getResourceHierarchy());
						TypeDeclaration srcComponent = componentMap.get(srcResourceName);
						TypeDeclaration dstComponent = componentMap.get(dstResourceName);
						DataTransferChannel ch = ((ChannelNode) resToCh.getDestination()).getChannel();
						for (ChannelMember out: ch.getOutputChannelMembers()) {
							if (dst.getInSideResources().contains(out.getResource())) {
								// Check if the input resource is outside of the channel scope.
								boolean outsideInputResource = false;
								for (ChannelMember cm: ch.getInputChannelMembers()) {
									if (cm.getResource().getResourceHierarchy().equals(src.getResourceHierarchy()) && cm.isOutside()) {
										outsideInputResource = true;	// Regarded as pull transfer.
										break;
									}
								}
								// Check if the output resource is outside of the channel scope.
								boolean outsideOutputResource = false;
								for (ChannelMember cm: ch.getOutputChannelMembers()) {
									if (cm.getResource().getResourceHierarchy().equals(dst.getResourceHierarchy()) && cm.isOutside()) {
										outsideOutputResource = true;	// Regarded as push transfer.
										break;
									}
								}
								if (pushPull.getOptions().get(0) == PushPullValue.PUSH && !outsideInputResource) {
									// for push data transfer
									MethodDeclaration update = getUpdateMethod(dstComponent, srcComponent);
									if (((StoreAttribute) dst.getAttribute()).isStored()) {
										// update stored state of dst side resource (when every incoming edge is in push style)
										Expression updateExp = null;
										if (ch.getReferenceChannelMembers().size() == 0) {
											updateExp = ch.deriveUpdateExpressionOf(out, JerseyCodeGenerator.pushAccessor);
										} else {
											// if there exists one or more reference channel member.
											HashMap<ChannelMember, IResourceStateAccessor> inputResourceToStateAccessor = new HashMap<>();
											for (Edge chToRes2: dst.getInEdges()) {
												DataTransferChannel ch2 = ((ChannelNode) chToRes2.getSource()).getChannel();
												for (Edge resToCh2: chToRes2.getSource().getInEdges()) {
													DataFlowEdge dIn = (DataFlowEdge) resToCh2;
													ChannelMember in = null;
													for (ChannelMember cm: ch2.getInputChannelMembers()) {
														if (cm.getResource().equals(((ResourceNode) dIn.getSource()).getOutSideResource())) {
															in = cm;
															break;
														}
													}
													inputResourceToStateAccessor.put(in, JerseyCodeGenerator.pushAccessor);
												}
											}
											for (ChannelMember c: ch.getReferenceChannelMembers()) {
												inputResourceToStateAccessor.put(c, JerseyCodeGenerator.pullAccessor);
											}
											updateExp = ch.deriveUpdateExpressionOf(out, JerseyCodeGenerator.pushAccessor, inputResourceToStateAccessor);
										}
										// Replace Json constructor with a constructor of the child resource.
										ResourceHierarchy outRes = out.getResource().getResourceHierarchy();
										if (outRes.getChildren().size() == 1 && outRes.getChildren().iterator().next().getNumParameters() > 0) {
											ResourceHierarchy childRes = outRes.getChildren().iterator().next();
											Type childStateType = childRes.getResourceStateType();
											String childComponentName = JerseyCodeGenerator.getComponentName(childRes);
											TypeDeclaration childComponent = componentMap.get(childComponentName);
											if (DataConstraintModel.typeJson.isAncestorOf(childStateType)) {
												replaceJsonTermWithConstructorInvocation(updateExp, childStateType, childComponentName, childComponent);
											}
										}
										// Add statements to the update method.
										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] + "this.value = " + curState + ";";
										}
										if (update.getBody() == null || !update.getBody().getStatements().contains(updateStatement)) {
											// add an update statement of the state of dst side resource.
											update.addFirstStatement(updateStatement);
										}
									}
									if (resToCh.getDestination().getIndegree() > 1) {
										// update a cash of src side resource (when incoming edges are multiple)
										String cashStatement = "this." + JerseyCodeGenerator.toVariableName(srcResourceName) + " = " + JerseyCodeGenerator.toVariableName(srcResourceName) + ";";
										if (update.getBody() == null || !update.getBody().getStatements().contains(cashStatement)) {
											update.addFirstStatement(cashStatement);
										}								
									}
									// to convert a json param to a tuple, pair or map 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.typePair.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(" + getCodeForConversionFromMapToPair(compType, "i") + ");\n";
												paramConverter += "}";
												update.addThrow("JsonProcessingException");
											} else if (DataConstraintModel.typeMap.isAncestorOf(compType)) {
												param.setType(DataConstraintModel.typeListStr);
												// To do.
											}
										} else if (DataConstraintModel.typeTuple.isAncestorOf(paramType)) {
											param.setType(DataConstraintModel.typeString);
											param.setName(paramName + "_json");
											paramConverter += paramType.getInterfaceTypeName() + " " + paramName + ";\n";
											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");
										} else if (DataConstraintModel.typePair.isAncestorOf(paramType)) {
											param.setType(DataConstraintModel.typeString);
											param.setName(paramName + "_json");
											paramConverter += paramType.getInterfaceTypeName() + " " + paramName + ";\n";
											paramConverter += "{\n";
											String mapTypeName = convertFromEntryToMapType(paramType);
											paramConverter += "\t" + mapTypeName + " i = new ObjectMapper().readValue(" + paramName + "_json" + ", HashMap.class);\n";
											paramConverter += "\t" + paramName + " = " + getCodeForConversionFromMapToPair(paramType, "i") + ";\n";
											paramConverter += "}";
											update.addThrow("JsonProcessingException");
										} else if (DataConstraintModel.typeMap.isAncestorOf(paramType)) {
											param.setType(DataConstraintModel.typeString);
											param.setName(paramName + "_json");
											paramConverter += paramType.getInterfaceTypeName() + " " + paramName + " = " + "new " + paramType.getImplementationTypeName() + "();\n";
											paramConverter += "{\n";
											String mapTypeName = convertFromEntryToMapType(paramType);
											paramConverter += "\t" + mapTypeName + " i = new ObjectMapper().readValue(" + paramName + "_json" + ", HashMap.class);\n";
											paramConverter += "\t" + getCodeForConversionFromMapToMap(paramType, "i", paramName) + "\n";
											paramConverter += "}";
											update.addThrow("JsonProcessingException");
										}
										if (paramConverter.length() > 0) update.addFirstStatement(paramConverter);
									}
									if (((StoreAttribute) dst.getAttribute()).isStored()) {
										// returns the state stored in a field.
										if (dstComponent == null) {
											String dstParentResourceName = JerseyCodeGenerator.getComponentName(dst.getResourceHierarchy().getParent());
											dstComponent = componentMap.get(dstParentResourceName);
										}
										MethodDeclaration getter = null;
										if (JerseyCodeGenerator.generatesComponent(dst.getResourceHierarchy())) {
											getter = getMethod(dstComponent, "getValue");
										} else {
											getter = getGetterMethod(dstComponent, dstResourceName);
										}
										if (getter.getBody() == null || getter.getBody().getStatements().size() == 0) {
											if (dst.getResourceHierarchy().getNumParameters() == 0) {
												getter.addStatement("return value;");
											} else {
												if (DataConstraintModel.typeList.isAncestorOf(dst.getParent().getResourceStateType())) {
													Term selector = new Term(DataConstraintModel.get);
													selector.addChild(new Field("value"));
													selector.addChild(dst.getSelectors().get(dst.getSelectors().size() - 1).getExpression());
													getter.addStatement("return " + selector.toImplementation(new String[] {}) + ";");
												} else if (DataConstraintModel.typeMap.isAncestorOf(dst.getParent().getResourceStateType())) {
													Term selector = new Term(DataConstraintModel.lookup);
													selector.addChild(new Field("value"));
													selector.addChild(dst.getSelectors().get(dst.getSelectors().size() - 1).getExpression());
													getter.addStatement("return " + selector.toImplementation(new String[] {}) + ";");
												}
											}
										}
									}
									// src side (for a chain of update method invocations)
									String httpMethod = null;
									if (out.getStateTransition().isRightUnary()) {
										httpMethod = "put";									
									} else {
										httpMethod = "post";
									}
									for (MethodDeclaration srcUpdate: getUpdateMethods(srcComponent)) {
										if (srcUpdate != null) {
											List<Map.Entry<Type, Map.Entry<String, String>>> params = new ArrayList<>();
											Set<ResourcePath> referredSet = referredResources.get(srcUpdate);
											if (ch.getReferenceChannelMembers().size() > 0) {
												for (ChannelMember rc: ch.getReferenceChannelMembers()) {
													// For each reference channel member, get the current state of the reference side resource by pull data transfer.
													ResourcePath ref = rc.getResource();
													if (referredSet == null) {
														referredSet = new HashSet<>();
														referredResources.put(srcUpdate, referredSet);
													}
													if (!dst.getInSideResources().contains(ref)) {
														String refResourceName = ref.getResourceName();
														Type refResourceType = ref.getResourceStateType();
														if (!referredSet.contains(ref)) {
															referredSet.add(ref);
															generatePullDataTransfer(srcUpdate, refResourceName, refResourceName, refResourceType);
														}
														// Value of a reference side resource.
														params.add(new AbstractMap.SimpleEntry<>(refResourceType, new AbstractMap.SimpleEntry<>(refResourceName, refResourceName)));
													}
												}
											}
											String srcResName = null;
											if (dst.getIndegree() > 1) {
												srcResName = srcResourceName;
											}
											if (!chainedCalls.contains(srcUpdate)) {
												// The first call to an update method in this method
												// Value of the source side (input side) resource.
												params.add(0, new AbstractMap.SimpleEntry<>(src.getResourceStateType(), new AbstractMap.SimpleEntry<>(srcResourceName, "this.value")));
												srcUpdate.addStatement(getHttpMethodParamsStatement(srcComponent.getTypeName(), params, true));
												srcUpdate.addStatement("String result = " + getHttpMethodCallStatement(baseURL, dstResourceName, srcResName, httpMethod));
												chainedCalls.add(srcUpdate);
											} else {
												// After the second time of call to update methods in this method
												// Value of the source side (input side) resource.
												params.add(0, new AbstractMap.SimpleEntry<>(src.getResourceStateType(), new AbstractMap.SimpleEntry<>(srcResourceName, "this.value")));
												srcUpdate.addStatement(getHttpMethodParamsStatement(srcComponent.getTypeName(), params, false));
												srcUpdate.addStatement("result = " + getHttpMethodCallStatement(baseURL, dstResourceName, srcResName, httpMethod));
											}
											srcUpdate.addThrow("JsonProcessingException");
										}
									}
									for (MethodDeclaration srcInput: getInputMethods(srcComponent, src, model)) {
										List<Map.Entry<Type, Map.Entry<String, String>>> params = new ArrayList<>();
										Set<ResourcePath> referredSet = referredResources.get(srcInput);
										for (ChannelMember rc: ch.getReferenceChannelMembers()) {
											// For each reference channel member, get the current state of the reference side resource by pull data transfer.
											ResourcePath ref = rc.getResource();
											if (referredSet == null) {
												referredSet = new HashSet<>();
												referredResources.put(srcInput, referredSet);
											}
											if (!dst.getInSideResources().contains(ref)) {
												String refResourceName = ref.getResourceName();
												Type refResourceType = ref.getResourceStateType();
												if (!referredSet.contains(ref)) {
													referredSet.add(ref);
													generatePullDataTransfer(srcInput, refResourceName, refResourceName, refResourceType);
												}
												// Value of a reference side resource.
												params.add(new AbstractMap.SimpleEntry<>(refResourceType, new AbstractMap.SimpleEntry<>(refResourceName, refResourceName)));
											}									
										}
										String srcResName = null;
										if (dst.getIndegree() > 1) {
											srcResName = srcResourceName;
										}
										if (!chainedCalls.contains(srcInput)) {
											// First call to an update method in this method
											// Value of the source side (input side) resource.
											params.add(0, new AbstractMap.SimpleEntry<>(src.getResourceStateType(), new AbstractMap.SimpleEntry<>(srcResourceName, "this.value")));
											srcInput.addStatement(getHttpMethodParamsStatement(srcComponent.getTypeName(), params, true));
											srcInput.addStatement("String result = " + getHttpMethodCallStatement(baseURL, dstResourceName, srcResName, httpMethod));
											chainedCalls.add(srcInput);
										} else {
											// After the second time of call to update methods in this method
											// Value of the source side (input side) resource.
											params.add(0, new AbstractMap.SimpleEntry<>(src.getResourceStateType(), new AbstractMap.SimpleEntry<>(srcResourceName, "this.value")));
											srcInput.addStatement(getHttpMethodParamsStatement(srcComponent.getTypeName(), params, false));
											srcInput.addStatement("result = " + getHttpMethodCallStatement(baseURL, dstResourceName, srcResName, httpMethod));
										}
										srcInput.addThrow("JsonProcessingException");
									}
								} else if ((pushPull.getOptions().get(0) != PushPullValue.PUSH && !outsideOutputResource) || outsideInputResource) {
									// for pull (or push/pull) data transfer
									if (dstComponent == null) {
										String dstParentResourceName = JerseyCodeGenerator.getComponentName(dst.getResourceHierarchy().getParent());
										dstComponent = componentMap.get(dstParentResourceName);
									}
									MethodDeclaration getter = null;
									if (JerseyCodeGenerator.generatesComponent(dst.getResourceHierarchy())) {
										getter = getMethod(dstComponent, "getValue");
									} else {
										getter = getGetterMethod(dstComponent, dstResourceName);
									}
									if (getter.getBody() == null || getter.getBody().getStatements().size() == 0) {
										// generate a return statement.
										Expression curExp = ch.deriveUpdateExpressionOf(out, JerseyCodeGenerator.pullAccessor);		// no pull data transfer is included.
										Map<ChannelMember, Entry<ResourcePath, Set<ChannelMember>>> resourcePaths = ch.fillOutsideResourcePaths(out, JerseyCodeGenerator.pullAccessor);
										String[] sideEffects = new String[] {""};
										String curState = curExp.toImplementation(sideEffects);
										getter.addStatement(sideEffects[0] + "return " + curState + ";");
										// For each reference channel member, get the current state of the reference side resource by pull data transfer.
										for (ChannelMember c: ((ChannelNode) resToCh.getDestination()).getChannel().getReferenceChannelMembers()) {
											String refResourceName = c.getResource().getResourceName();
											Type refResourceType = c.getResource().getResourceStateType();
											generatePullDataTransfer(getter, refResourceName, refResourceName, refResourceType);
										}
										for (Entry<ChannelMember, Entry<ResourcePath, Set<ChannelMember>>> pathEnt: resourcePaths.entrySet()) {
											ChannelMember cm = pathEnt.getKey();
											ResourcePath src2 = pathEnt.getValue().getKey();
											// get outside src resource state by pull data transfer.
											if (cm.isOutside() || src2.getCommonPrefix(dst.getOutSideResource()) == null) {
												Type srcResourceType = src2.getResourceStateType();
												List<String> pathParams = new ArrayList<>();
												for (Expression pathExp: src2.getPathParams()) {
													pathParams.add("\" + " + pathExp.toImplementation(sideEffects) + " + \"");
												}
												generatePullDataTransfer(getter, src2.getResourceName(), src2.getResourceHierarchy().toResourcePath(pathParams), srcResourceType);
											}
										}
									}
									// get src resource state by pull data transfer.
									if (src.getNumberOfParameters() == 0 && src.getOutSideResource().getCommonPrefix(dst.getOutSideResource()) == null) {
										Type srcResourceType = src.getResourceStateType();
										List<String> pathParams = new ArrayList<>();
										generatePullDataTransfer(getter, src.getResourceName(), src.getResourceHierarchy().toResourcePath(pathParams), srcResourceType);
									}
								} 
							}
						}
					}
				}
			}
			// for source nodes
			for (ResourceHierarchy resource: model.getResourceHierarchies()) {
				String resourceName = JerseyCodeGenerator.getComponentName(resource);
				TypeDeclaration component = componentMap.get(resourceName);
				if (component != null) {
					// state getter method
					Type resourceType = JerseyCodeGenerator.getImplStateType(resource);
					MethodDeclaration stateGetter = getMethod(component, "getValue");
					if (stateGetter.getBody() == null || stateGetter.getBody().getStatements().size() == 0) {
						if (model.isPrimitiveType(resourceType)) {
							// primitive type
							stateGetter.addStatement("return value;");
						} else {
							if (resource.getChildren() != null && resource.getChildren().size() == 1 && resource.getChildren().iterator().next().getNumParameters() > 0) {
								// list or map
								String implTypeName = resourceType.getImplementationTypeName();
								// copy the current state to be returned as a 'value'
								stateGetter.addStatement("return new " + implTypeName + "(value);");
							} else {
								if (resource.getChildren() == null || resource.getChildren().size() == 0) {
									// a leaf resource
									String implTypeName = resourceType.getImplementationTypeName();
									stateGetter.addStatement("return new " + implTypeName + "(value);");
								} else {
									Term composer = null; 
									Term composerSub = new Constant(DataConstraintModel.nil);
									composerSub.setType(DataConstraintModel.typeMap);
									for (ResourceHierarchy child: resource.getChildren()) {
										String childTypeName = JerseyCodeGenerator.getComponentName(child);
										String fieldName = JerseyCodeGenerator.toVariableName(childTypeName);
										Term childGetter = null; 
										if ((child.getChildren() == null || child.getChildren().size() == 0) && child.getNumParameters() == 0) {
											// the child is not a class
											childGetter = new Term(new Symbol("get" + childTypeName, 1, Symbol.Type.METHOD));
											childGetter.addChild(new Constant("this"));
										} else {
											// the child is a class
											childGetter = new Term(new Symbol("getValue", 1, Symbol.Type.METHOD));
											childGetter.addChild(new Field(fieldName, JerseyCodeGenerator.getImplStateType(child)));
										}
										composer = new Term(DataConstraintModel.insert);
										composer.addChild(composerSub);
										composer.addChild(new Constant("\"" + fieldName + "\"", DataConstraintModel.typeString));	// key
										composer.addChild(childGetter);																// value
										composer.setType(DataConstraintModel.typeMap);
										composerSub = composer;
									}
									composer.setType(stateGetter.getReturnType());
									String[] sideEffects = new String[] {null};
									String returnValue = composer.toImplementation(sideEffects);
									if (sideEffects[0] != null) {
										stateGetter.addStatement(sideEffects[0] + "return " + returnValue+ ";");
									} else {
										stateGetter.addStatement("return " + returnValue+ ";");
									}
								}
							}
						}
					}
					
					// child getter method
					if (resource.getChildren().size() > 0) {
						for (ResourceHierarchy child: resource.getChildren()) {
							String methodName = "get" + JerseyCodeGenerator.getComponentName(child);
							MethodDeclaration childGetter = getMethod(component, methodName);
							if (childGetter != null && (childGetter.getBody() == null || childGetter.getBody().getStatements().size() == 0)) {
								if (DataConstraintModel.typeList.isAncestorOf(resource.getResourceStateType())) {
									Term selector = new Term(DataConstraintModel.get);
									selector.addChild(new Field("value"));
									selector.addChild(new Variable(childGetter.getParameters().get(childGetter.getParameters().size() - 1).getName()));
									selector.setType(childGetter.getReturnType());
									String[] sideEffects = new String[] {null};
									String returnValue = selector.toImplementation(sideEffects);
									if (sideEffects[0] != null) childGetter.addStatement(sideEffects[0]);
									childGetter.addStatement("return " + returnValue + ";");
								} else if (DataConstraintModel.typeMap.isAncestorOf(resource.getResourceStateType())) {
									Term selector = new Term(DataConstraintModel.lookup);
									selector.addChild(new Field("value"));
									selector.addChild(new Variable(childGetter.getParameters().get(childGetter.getParameters().size() - 1).getName()));
									selector.setType(childGetter.getReturnType());
									String[] sideEffects = new String[] {null};
									String returnValue = selector.toImplementation(sideEffects);
									if (sideEffects[0] != null) childGetter.addStatement(sideEffects[0]);
									childGetter.addStatement("return " + returnValue+ ";");
								} else {
									String fieldName = JerseyCodeGenerator.getComponentName(child);
									String returnValue = JerseyCodeGenerator.toVariableName(fieldName);
									childGetter.addStatement("return this." + returnValue + ";");
								}
							}
						}
					}					
				}
				
				// methods for input events
				Map<DataTransferChannel, Set<ChannelMember>> ioChannelsAndMembers = getIOChannelsAndMembers(resource, model);
				for (Map.Entry<DataTransferChannel, Set<ChannelMember>> entry: ioChannelsAndMembers.entrySet()) {
					Set<ChannelMember> outs = entry.getValue();
					for (ChannelMember out: outs) {
						MethodDeclaration input = null;
						if (JerseyCodeGenerator.generatesComponent(resource)) {
							// A component is generated for this resource.
							input = getInputMethod(component, out);
						} else {
							// No component is generated for this resource.
							ResourceHierarchy parent = resource.getParent();
							if (parent != null) {
								TypeDeclaration parentType = componentMap.get(JerseyCodeGenerator.getComponentName(parent));
								input = getInputMethod(parentType, out);
							}
						}
						if (input != null) {
							// In each resource
							Expression updateExp = entry.getKey().deriveUpdateExpressionOf(out, JerseyCodeGenerator.pushAccessor);
							// Replace Json constructor with a constructor of the child resource.
							ResourceHierarchy outRes = out.getResource().getResourceHierarchy();
							if (outRes.getChildren().size() == 1 && outRes.getChildren().iterator().next().getNumParameters() > 0) {
								ResourceHierarchy childRes = outRes.getChildren().iterator().next();
								Type childStateType = childRes.getResourceStateType();
								String childComponentName = JerseyCodeGenerator.getComponentName(childRes);
								TypeDeclaration childComponent = componentMap.get(childComponentName);
								if (DataConstraintModel.typeJson.isAncestorOf(childStateType)) {
									replaceJsonTermWithConstructorInvocation(updateExp, childStateType, childComponentName, childComponent);
								}
							}
							// Add statements to the input method.
							String[] sideEffects = new String[] {""};
							String newState = updateExp.toImplementation(sideEffects);
							if (JerseyCodeGenerator.generatesComponent(resource)) {
								String updateStatement;
								if (updateExp instanceof Term && ((Term) updateExp).getSymbol().isImplWithSideEffect()) {
									updateStatement = sideEffects[0];								
								} else {
									updateStatement = sideEffects[0] + "this.value = " + newState + ";";
								}
								if (input.getBody() == null || !input.getBody().getStatements().contains(updateStatement)) {
									input.addFirstStatement(updateStatement);
								}
							} else {
								String updateStatement = null;
								if (updateExp instanceof Term && ((Term) updateExp).getSymbol().isImplWithSideEffect()) {
									// ToDo.
									updateStatement = sideEffects[0];	
								} else {
									if (DataConstraintModel.typeList.isAncestorOf(resource.getParent().getResourceStateType())) {
										Term selector = new Term(DataConstraintModel.set);
										selector.addChild(new Field("value"));
										selector.addChild(new Variable(input.getParameters().get(input.getParameters().size() - 2).getName()));
										selector.addChild(new Constant(newState));
										String[] sideEffects2 = new String[] {""};
										String newList = selector.toImplementation(sideEffects2);
										updateStatement = sideEffects[0] + sideEffects2[0];
									} else if (DataConstraintModel.typeMap.isAncestorOf(resource.getParent().getResourceStateType())) {
										Term selector = new Term(DataConstraintModel.insert);
										selector.addChild(new Field("value"));
										selector.addChild(new Variable(input.getParameters().get(input.getParameters().size() - 2).getName()));
										selector.addChild(new Constant(newState));
										String[] sideEffects2 = new String[] {""};
										String newMap = selector.toImplementation(sideEffects2);
										updateStatement = sideEffects[0] + sideEffects2[0];
									} else {
										updateStatement = sideEffects[0] + "this." + JerseyCodeGenerator.toVariableName(JerseyCodeGenerator.getComponentName(resource)) + " = " + newState + ";";
									}
									if (updateStatement != null && (input.getBody() == null || !input.getBody().getStatements().contains(updateStatement))) {
										input.addFirstStatement(updateStatement);
									}
								}
							}
							
							if (out.getResource().getParent() != null && out.getResource().getParent().getParent() != null) {
								// In the root resource
								MethodDeclaration inputAccessor = getMethod(componentMap.get(JerseyCodeGenerator.getComponentName(resource.getRoot())), input.getName());
								if (inputAccessor != null) {
									Expression resExp = JerseyCodeGenerator.pullAccessor.getDirectStateAccessorFor(out.getResource(), out.getResource().getRoot());
									String args = "";
									String delimiter = "";
									if (resExp instanceof Term) {
										// to access the parent
										if (((Term) resExp).getChildren().size() > 1 && ((Term) resExp).getChild(1) instanceof Variable) {
											args += delimiter + ((Variable)((Term) resExp).getChild(1)).getName();
											delimiter = ", ";
										}
										resExp = ((Term) resExp).getChild(0);
									}
									String resourceAccess = resExp.toImplementation(new String[] {null});
									if (out.getStateTransition().getMessageExpression() instanceof Term) {
										Term message = (Term) out.getStateTransition().getMessageExpression();
										for (Variable var: message.getVariables().values()) {
											args += delimiter + var.getName();
											delimiter = ", ";
										}
									}
									inputAccessor.addStatement(resourceAccess + "." + input.getName() + "(" + args + ");");
								}
							}
						}
					}
				}
			}
		} catch (ParameterizedIdentifierIsFutureWork | ResolvingMultipleDefinitionIsFutureWork
				| InvalidMessage | UnificationFailed | ValueUndefined e1) {
			e1.printStackTrace();
		}
		return codes;
	}

	private static void replaceJsonTermWithConstructorInvocation(Expression exp, Type replacedJsonType, String replacingClassName, TypeDeclaration childComponent) {
		Map<Position, Term> subTerms = ((Term) exp).getSubTerms(Term.class);
		Iterator<Entry<Position, Term>> termEntItr = subTerms.entrySet().iterator();
		while (termEntItr.hasNext()) {
			Entry<Position, Term> termEnt = termEntItr.next();
			Term jsonTerm = termEnt.getValue();
			if (jsonTerm.getType().equals(replacedJsonType)) {
				String constructorInvocation = "new " + replacingClassName + "(";
				MethodDeclaration childConstructor = getConstructor(childComponent);
				String delimiter = "";
				for (VariableDeclaration var: childConstructor.getParameters()) {
					JsonAccessor jsonMember = new JsonAccessor(DataConstraintModel.dot);
					jsonMember.addChild(jsonTerm);
					jsonMember.addChild(new Constant("\"" + var.getName() + "\""));
					Expression param = jsonMember.reduce();
					if (param != null) {
						constructorInvocation = constructorInvocation + delimiter + param.toImplementation(new String[] {""});
					} else {
						constructorInvocation = constructorInvocation + delimiter + var.getName();
					}
					delimiter = ", ";
				}
				constructorInvocation += ")";
				((Term) exp).replaceSubTerm(termEnt.getKey(), new Constant(constructorInvocation));
				subTerms = ((Term) exp).getSubTerms(Term.class);
				termEntItr = subTerms.entrySet().iterator();
			}
		}
	}

	private static void generatePullDataTransfer(MethodDeclaration methodBody, String fromResourceName, String fromResourcePath, Type fromResourceType) {
		String varName = new String(fromResourceName);
		String respTypeName = fromResourceType.getInterfaceTypeName();
		String respImplTypeName = fromResourceType.getImplementationTypeName();
		String respConverter = "";
		if (DataConstraintModel.typeList.isAncestorOf(fromResourceType) && fromResourceType != DataConstraintModel.typeList) {
			Type compType = TypeInference.getListComponentType(fromResourceType);
			if (DataConstraintModel.typeTuple.isAncestorOf(compType)) {
				varName += "_json";
				String mapTypeName = convertFromEntryToMapType(compType);
				respTypeName = "List<" + mapTypeName + ">";
				respConverter += fromResourceType.getInterfaceTypeName() + " " + fromResourceName + " = new " + fromResourceType.getImplementationTypeName() + "();\n";
				respConverter += "for (" + mapTypeName + " i: " + varName + ") {\n";
				respConverter += "\t" + fromResourceName + ".add(" + getCodeForConversionFromMapToTuple(compType, "i") + ");\n";
				respConverter += "}";
				methodBody.addThrow("JsonProcessingException");
			} else if (DataConstraintModel.typeMap.isAncestorOf(compType)) {
				// To do.
			}
		} else if (DataConstraintModel.typeTuple.isAncestorOf(fromResourceType)) {
			varName += "_json";
			respTypeName = convertFromEntryToMapType(fromResourceType);
			respConverter += fromResourceType.getInterfaceTypeName() + " " + fromResourceName + " = " + getCodeForConversionFromMapToTuple(fromResourceType, varName) + ";";
			respImplTypeName = "HashMap";
		} else if (DataConstraintModel.typePair.isAncestorOf(fromResourceType)) {
			varName += "_json";
			respTypeName = convertFromEntryToMapType(fromResourceType);
			respConverter += fromResourceType.getInterfaceTypeName() + " " + fromResourceName + " = " + getCodeForConversionFromMapToPair(fromResourceType, varName) + ";";
			respImplTypeName = "HashMap";
		} else if (DataConstraintModel.typeMap.isAncestorOf(fromResourceType)) {
			varName += "_json";
			respTypeName = convertFromEntryToMapType(fromResourceType);
			respConverter += fromResourceType.getInterfaceTypeName() + " " + fromResourceName + " = new " + fromResourceType.getImplementationTypeName() + "();\n";
			respConverter += getCodeForConversionFromMapToMap(fromResourceType, varName, fromResourceName);
			respImplTypeName = "HashMap";
		}
		if (respConverter.length() > 0) {
			methodBody.addFirstStatement(respConverter);
		}
		methodBody.addFirstStatement(respTypeName + " " + varName + " = " + getHttpMethodCallStatementWithResponse(baseURL, fromResourcePath, "get", respImplTypeName));
	}

	private static String convertFromEntryToMapType(Type type) {
		String mapTypeName = null;
		if (DataConstraintModel.typePair.isAncestorOf(type)) {
			Type compType = TypeInference.getPairComponentType(type);
			String wrapperType = DataConstraintModel.getWrapperType(compType);
			if (wrapperType != null) {
				mapTypeName = "Map<String, " + wrapperType + ">";
			} else {
				mapTypeName = "Map<String, " + compType.getInterfaceTypeName() + ">";
			}
		} else if (DataConstraintModel.typeMap.isAncestorOf(type)) {
			List<Type> compTypes = TypeInference.getMapComponentTypes(type);
			String wrapperType = DataConstraintModel.getWrapperType(compTypes.get(1));
			if (wrapperType != null) {
				mapTypeName = "Map<String, " + wrapperType + ">";
			} else {
				mapTypeName = "Map<String, " + compTypes.get(1).getInterfaceTypeName() + ">";
			}
		} else {
			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
					|| elmType == DataConstraintModel.typeInt
					|| elmType == DataConstraintModel.typeLong
					|| elmType == DataConstraintModel.typeFloat
					|| elmType == DataConstraintModel.typeDouble) {
				String elmVal = CodeUtil.getToValueExp(elmType.getImplementationTypeName(), elementBase + ".getKey()");
				decoded = decoded.replace("$x", "new AbstractMap.SimpleEntry<>(" + elmVal + ", $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 getCodeForConversionFromMapToPair(Type pairType, String mapVar) {
		String decoded = "$x";
		decoded = decoded.replace("$x", "new Pair<>(" + mapVar + ".get(\"left\"), $x)");
		decoded = decoded.replace("$x", mapVar + ".get(\"right\")");
		return decoded;
	}

	private static String getCodeForConversionFromMapToMap(Type mapType, String mapVal, String mapVar) {
		List<Type> elementsTypes = TypeInference.getMapComponentTypes(mapType);
		Type keyType = elementsTypes.get(0);
		Type valType = elementsTypes.get(1);
		String keyVal = null;
		String decoded = "";
		if (keyType == DataConstraintModel.typeBoolean
				|| keyType == DataConstraintModel.typeInt
				|| keyType == DataConstraintModel.typeLong
				|| keyType == DataConstraintModel.typeFloat
				|| keyType == DataConstraintModel.typeDouble) {
			decoded += "for (String k: " + mapVal + ".keySet()) {\n";
			decoded += "\t" + mapVar + ".put(";
			keyVal = CodeUtil.getToValueExp(keyType.getImplementationTypeName(), "k");
			decoded += keyVal + ", " + mapVal + ".get(" + keyVal + ")" + ");\n";
			decoded += "}";
		} else if (keyType == DataConstraintModel.typeString) {
			decoded += mapVar + " = " + mapVal + ";";
		}
		return decoded;
	}

	private static String getHttpMethodParamsStatement(String callerResourceName, List<Map.Entry<Type, Map.Entry<String, String>>> params, boolean isFirstCall) {
		String statements = "";
		if (isFirstCall) {
			statements += "Form ";
		}
		statements += "form = new Form();\n";
		for (Map.Entry<Type, Map.Entry<String, String>> param: params) {
			Type paramType = param.getKey();
			String paramName = param.getValue().getKey();
			String value = param.getValue().getValue();
			if (DataConstraintModel.typeList.isAncestorOf(paramType)) {
				Type compType = TypeInference.getListComponentType(paramType);
				String wrapperType = DataConstraintModel.getWrapperType(compType);
				if (wrapperType == null) {
					statements += "for (" + compType.getInterfaceTypeName() + " i: " + value + ") {\n";
				} else {
					statements += "for (" + wrapperType + " i: " + value + ") {\n";
				}
				if (DataConstraintModel.typeTuple.isAncestorOf(compType) || DataConstraintModel.typePair.isAncestorOf(paramType) || DataConstraintModel.typeList.isAncestorOf(compType) || DataConstraintModel.typeMap.isAncestorOf(paramType)) {
					statements += "\tform.param(\"" + paramName + "\", new ObjectMapper().writeValueAsString(i));\n";		// typeTuple: {"1.0":2.0},  typePair: {"left": 1.0, "right":2.0}
				} else {
					statements += "\tform.param(\"" + paramName + "\", i.toString());\n";
				}
				statements += "}\n";
//				return "Entity<String> entity = Entity.entity(" + paramName + ".toString(), MediaType.APPLICATION_JSON);";
			} else if (DataConstraintModel.typeTuple.isAncestorOf(paramType) || DataConstraintModel.typePair.isAncestorOf(paramType) || DataConstraintModel.typeMap.isAncestorOf(paramType)) {
				// typeTuple: {"1.0":2.0},  typePair: {"left": 1.0, "right":2.0}
				statements += "form.param(\"" + paramName + "\", new ObjectMapper().writeValueAsString(" + value + "));\n";			
			} else {
				statements += "form.param(\"" + paramName + "\", " + CodeUtil.getToStringExp(paramType.getImplementationTypeName(), value) + ");\n";
			}
		}
		if (isFirstCall) {
			statements += "Entity<Form> ";
		}
		statements += "entity = Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED);";
		return statements;
	}

	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 getConstructor(TypeDeclaration component) {
		for (MethodDeclaration m: component.getMethods()) {
			if (m.isConstructor()) return m;
		}
		return null;
	}

	private static MethodDeclaration getUpdateMethod(TypeDeclaration component, TypeDeclaration from) {
		for (MethodDeclaration m: component.getMethods()) {
			if (m.getName().equals("update" + from.getTypeName())) return m;
		}
		return null;
	}
	
	private static MethodDeclaration getGetterMethod(TypeDeclaration component, String resourceName) {
		for (MethodDeclaration m: component.getMethods()) {
			if (m.getName().startsWith("get" + resourceName)) return m;
		}
		return null;
	}
	
	private static List<MethodDeclaration> getUpdateMethods(TypeDeclaration component) {
		List<MethodDeclaration> updates = new ArrayList<>();
		for (MethodDeclaration m: component.getMethods()) {
			if (m.getName().startsWith("update")) {
				updates.add(m);
			}
		}
		return updates;
	}
	
	private static Map<DataTransferChannel, Set<ChannelMember>> getIOChannelsAndMembers(ResourceHierarchy resource, DataTransferModel model) {
		Map<DataTransferChannel, Set<ChannelMember>> ioChannelsAndMembers = new HashMap<>();
		for (Channel c: model.getIOChannels()) {
			DataTransferChannel ch = (DataTransferChannel) c;
			// I/O channel
			for (ChannelMember out: ch.getOutputChannelMembers()) {
				if (resource.equals(out.getResource().getResourceHierarchy())) {
					if (out.getStateTransition().getMessageExpression() instanceof Term || out.getStateTransition().getMessageExpression() instanceof Variable) {
						Set<ChannelMember> channelMembers = ioChannelsAndMembers.get(ch);
						if (channelMembers == null) {
							channelMembers = new HashSet<>();
							ioChannelsAndMembers.put(ch, channelMembers);
						}
						channelMembers.add(out);
					}
				}
			}
		}
		return ioChannelsAndMembers;
	}

	private static List<MethodDeclaration> getInputMethods(TypeDeclaration component, ResourceNode resource, DataTransferModel model) {
		List<MethodDeclaration> inputs = new ArrayList<>();
		for (Channel c: model.getIOChannels()) {
			DataTransferChannel channel = (DataTransferChannel) c;
			// I/O channel
			for (ChannelMember out: channel.getOutputChannelMembers()) {
				if (resource.getInSideResources().contains(out.getResource())) {
					MethodDeclaration input = getInputMethod(component, out);
					inputs.add(input);
				}
			}
		}
		return inputs;
	}

	private static MethodDeclaration getInputMethod(TypeDeclaration component, ChannelMember out) {
		MethodDeclaration input = null;
		if (out.getStateTransition().getMessageExpression() instanceof Term) {
			Term message = (Term) out.getStateTransition().getMessageExpression();
			input = getMethod(component, message.getSymbol().getImplName());
		} else if (out.getStateTransition().getMessageExpression() instanceof Variable) {
			Variable message = (Variable) out.getStateTransition().getMessageExpression();
			input = getMethod(component, message.getName());
		}
		return input;
	}

	private static MethodDeclaration getMethod(TypeDeclaration component, String methodName) {
		for (MethodDeclaration m: component.getMethods()) {
			if (m.getName().equals(methodName)) return m;
		}
		return null;
	}
}