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.Parameter;
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.dataConstraintModel.Selector;
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;
								ChannelMember in = null;
								for (ChannelMember cm: ch.getInputChannelMembers()) {
									if (src.getOutSideResources().contains(cm.getResource())) {
										in = cm;
										if (cm.isOutside()) {
											outsideInputResource = true;	// Regarded as pull transfer.
											break;
										}
									}
								}
								// Check if the output resource is outside of the channel scope.
								boolean outsideOutputResource = out.isOutside();
								if ((pushPull.getOptions().get(0) == PushPullValue.PUSH && !outsideInputResource) || outsideOutputResource) {
									// for push data transfer
									MethodDeclaration update = null;
									if (dstComponent == null) {
										String dstParentResourceName = JerseyCodeGenerator.getComponentName(dst.getResourceHierarchy().getParent());
										dstComponent = componentMap.get(dstParentResourceName);
										update = getUpdateMethod(dstComponent, dstResourceName, srcResourceName);
									} else {
										update = getUpdateMethod(dstComponent, null, srcResourceName);
									}
									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).getKey();
										} 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 in2 = null;
													for (ChannelMember cm: ch2.getInputChannelMembers()) {
														if (((ResourceNode) dIn.getSource()).getOutSideResources().contains(cm.getResource())) {
															in2 = cm;
															break;
														}
													}
													inputResourceToStateAccessor.put(in2, JerseyCodeGenerator.pushAccessor);
												}
											}
											for (ChannelMember c: ch.getReferenceChannelMembers()) {
												inputResourceToStateAccessor.put(c, JerseyCodeGenerator.refAccessor);
											}
											updateExp = ch.deriveUpdateExpressionOf(out, JerseyCodeGenerator.pushAccessor, inputResourceToStateAccessor).getKey();
										}
										// 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 newState = updateExp.toImplementation(sideEffects);
										int numOfOutResourcesWithTheSameHierarchy = 0;
										for (ResourcePath outResPath: ch.getOutputResources()) {
											if (outResPath.getResourceHierarchy().equals(outRes)) {
												numOfOutResourcesWithTheSameHierarchy++;
											}
										}
										String updateStatement = "";
										if (JerseyCodeGenerator.generatesComponent(outRes)) {
											if (updateExp instanceof Term && ((Term) updateExp).getSymbol().isImplWithSideEffect()) {
												updateStatement = sideEffects[0];								
											} else {
												updateStatement = sideEffects[0] + "this.value = " + newState + ";";
											}
										} else {
											if (sideEffects[0] != null) {
												updateStatement = sideEffects[0];	
												updateStatement = updateStatement.replace(".value", "." + JerseyCodeGenerator.toVariableName(JerseyCodeGenerator.getComponentName(outRes)));
											}
											if (DataConstraintModel.typeList.isAncestorOf(outRes.getParent().getResourceStateType())) {
												Term selector = new Term(DataConstraintModel.set);
												selector.addChild(new Field("value"));
												selector.addChild(new Variable(update.getParameters().get(update.getParameters().size() - 2).getName()));
												selector.addChild(new Constant(newState));
												String[] sideEffects2 = new String[] {""};
												String newList = selector.toImplementation(sideEffects2);
												updateStatement += sideEffects2[0];
											} else if (DataConstraintModel.typeMap.isAncestorOf(outRes.getParent().getResourceStateType())) {
												Term selector = new Term(DataConstraintModel.insert);
												selector.addChild(new Field("value"));
												selector.addChild(new Variable(update.getParameters().get(update.getParameters().size() - 2).getName()));
												selector.addChild(new Constant(newState));
												String[] sideEffects2 = new String[] {""};
												String newMap = selector.toImplementation(sideEffects2);
												updateStatement += sideEffects2[0];
											} else if (!(updateExp instanceof Term && ((Term) updateExp).getSymbol().isImplWithSideEffect())) {
												updateStatement += "this." + JerseyCodeGenerator.toVariableName(JerseyCodeGenerator.getComponentName(outRes)) + " = " + newState + ";";
											}
										}
										// add an update statement of the state of dst side resource.
										if (numOfOutResourcesWithTheSameHierarchy == 1) {
											update.addFirstStatement(updateStatement);
										} else {
											Term conditions = null;
											int v = 1;
											Map<ChannelMember, Entry<ResourcePath, Set<ChannelMember>>> resourcePaths = ch.fillOutsideResourcePaths(out, JerseyCodeGenerator.pushAccessor);
											for (Expression pathParam: out.getResource().getPathParams()) {
												if (pathParam instanceof Variable) {
													String selfParamName = ((Variable) pathParam).getName();
													Expression arg = null;
													for (Selector selector: ch.getAllSelectors()) {
														if (selector.getExpression() instanceof Variable) {
															Variable selVar = (Variable) selector.getExpression();
															if (selVar.getName().equals(selfParamName)) {
																arg = selVar;
																break;
															}
														}
													}
													if (arg == null) {
														ResourcePath filledPath = resourcePaths.get(out).getKey();
														arg = filledPath.getPathParams().get(v - 1);
													}
													Term condition = new Term(DataConstraintModel.eq, new Expression[] {
															new Parameter("self" + (v > 1 ? v : "")), 
															arg});
													if (conditions == null) {
														conditions = condition;
													} else {
														conditions = new Term(DataConstraintModel.and, new Expression[] {
																conditions, 
																condition});
													}
												}
												v++;
											}
											String ifStatement = "if (" + conditions.toImplementation(new String[] {})+ ") {\n";
											update.addFirstStatement(ifStatement + "\t" + updateStatement.replace("\n", "\n\t") + "\n}");
										}
									}
									// Calculate in-degree of the destination resource.
									Set<ResourceHierarchy> inResources = new HashSet<>();
									for (ResourceNode rn: graph.getResourceNodes(out.getResource().getResourceHierarchy())) {
										// ResourceNodes that have the same ResourceHierarchy.
										for (Edge chToRes2: rn.getInEdges()) {
											for (Edge resToCh2: chToRes2.getSource().getInEdges()) {
												inResources.add(((ResourceNode) resToCh2.getSource()).getResourceHierarchy());
											}
										}
									}
									int inDegree = inResources.size();
									if (inDegree > 1 
											|| (inDegree == 1 && ch.getInputChannelMembers().iterator().next().getStateTransition().isRightPartial())) {
										// update a cache of src side resource (when incoming edges are multiple)
										String cacheStatement = "this." + JerseyCodeGenerator.toVariableName(srcResourceName) + " = " + JerseyCodeGenerator.toVariableName(srcResourceName) + ";";
										if (update.getBody() == null || !update.getBody().getStatements().contains(cacheStatement)) {
											update.addStatement(cacheStatement);
										}
									}
									// For a post/put REST API.
									if (outsideOutputResource 
											|| (in.getResource().getCommonPrefix(out.getResource()) == null && JerseyCodeGenerator.differentTreesAsDifferentServices)) {
										// Inter-services
										if (dst.getResourceHierarchy().getParent() != null) {
											// If not a root resource.
											TypeDeclaration rootComponent = componentMap.get(JerseyCodeGenerator.getComponentName(dst.getResourceHierarchy().getRoot()));
											MethodDeclaration update2 = update;
											update = getMethod(rootComponent, update2.getName());		// get the accessor to the update method.
											// To make the accessor call the update method.
											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});
											int v = 0;
											for (VariableDeclaration var: update2.getParameters()) {
												if (v < out.getResource().getPathParams().size()) {
													args += delimiter + ((Variable) out.getResource().getPathParams().get(v)).getName();
												} else {
													args += delimiter + var.getName();
												}
												delimiter = ", ";
												v++;
											}
											update.addStatement(resourceAccess + "." + update2.getName() + "(" + args + ");");
										}
										// 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.getBody().getStatements().contains(paramConverter)) {
												update.addFirstStatement(paramConverter);
											}
										}
									}
									if (((StoreAttribute) dst.getAttribute()).isStored()) {
										// returns the state stored in a field.
										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) {
												if (JerseyCodeGenerator.generatesComponent(dst.getResourceHierarchy())) {
													// dst has a component.
													getter.addStatement("return value;");
												} else {
													// dst has no component.
													String dstResName = JerseyCodeGenerator.toVariableName(JerseyCodeGenerator.getComponentName(dst.getResourceHierarchy()));
													getter.addStatement("return " + dstResName + ";");
												}
											} 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";
									}
									String srcName = null;
									if (srcComponent == null) {
										String srcParentResourceName = JerseyCodeGenerator.getComponentName(src.getResourceHierarchy().getParent());
										srcComponent = componentMap.get(srcParentResourceName);
										srcName = srcResourceName;
									}
									for (MethodDeclaration srcUpdate: getUpdateMethods(srcComponent, srcName)) {
										if (srcUpdate != null) {
											List<Map.Entry<Type, Map.Entry<String, String>>> params = new ArrayList<>();
											ResourcePath dstRes = out.getResource();
											// Values of channel parameters.
											for (Selector selector: ch.getAllSelectors()) {
												if (selector.getExpression() instanceof Variable) {
													Variable selVar = (Variable) selector.getExpression();
													params.add(new AbstractMap.SimpleEntry<>(selVar.getType(), 
																								new AbstractMap.SimpleEntry<>(selVar.getName(), selVar.getName())));
												}
											}
											// Value of the source side (input side) resource.
											String srcFieldName = "this.value";
											if (!JerseyCodeGenerator.generatesComponent(src.getResourceHierarchy())) {
												srcFieldName = JerseyCodeGenerator.toVariableName(srcResourceName);
											}
											params.add(new AbstractMap.SimpleEntry<>(src.getResourceStateType(), 
																						new AbstractMap.SimpleEntry<>(JerseyCodeGenerator.toVariableName(srcResourceName), srcFieldName)));
											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.getLeafResourceName();
														Type refResourceType = ref.getResourceStateType();
														if (!referredSet.contains(ref)) {
															referredSet.add(ref);
															String[] sideEffects = new String[] {""};
															if (rc.isOutside()) {
																List<String> pathParams = new ArrayList<>();
																for (Expression pathExp: ref.getPathParams()) {
																	pathParams.add("\" + " + pathExp.toImplementation(sideEffects) + " + \"");
																}															
																generatePullDataTransfer(srcUpdate, refResourceName, ref.getResourceHierarchy().toResourcePath(pathParams), refResourceType);
															} else {
																ResourcePath srcRes = in.getResource();
																if (!JerseyCodeGenerator.generatesComponent(srcRes.getResourceHierarchy())) {
																	srcRes = srcRes.getParent();
																}
																Expression refGetter = JerseyCodeGenerator.pullAccessor.getDirectStateAccessorFor(ref, srcRes);
																String refExp = refGetter.toImplementation(sideEffects);
																String refTypeName = ref.getResourceStateType().getInterfaceTypeName();
																srcUpdate.addFirstStatement(sideEffects[0] + refTypeName + " " + refResourceName + " = " + refExp + ";");
															}
														}
														// Value of a reference side resource.
														params.add(new AbstractMap.SimpleEntry<>(refResourceType, new AbstractMap.SimpleEntry<>(refResourceName, refResourceName)));
													}
												}
											}
											if (outsideOutputResource || (in.getResource().getCommonPrefix(dstRes) == null && JerseyCodeGenerator.differentTreesAsDifferentServices)) {
												// Inter-servces
												String[] sideEffects = new String[] {""};
												List<String> pathParams = new ArrayList<>();
												for (Expression pathExp: dstRes.getPathParams()) {
													pathParams.add("\" + " + pathExp.toImplementation(sideEffects) + " + \"");
												}
												String srcResName = JerseyCodeGenerator.toVariableName(srcResourceName);
												if (inDegree <= 1) {
													srcResName = null;
												}
												if (!chainedCalls.contains(srcUpdate)) {
													// The first call to an update method in this method
													srcUpdate.addStatement(getHttpMethodParamsStatement(srcComponent.getTypeName(), params, true));
													srcUpdate.addStatement("String result = " + getHttpMethodCallStatement(baseURL, 
																															dstRes.getResourceHierarchy().toResourcePath(pathParams), 
																															srcResName, 
																															httpMethod));
													chainedCalls.add(srcUpdate);
												} else {
													// After the second time of call to update methods in this method
													srcUpdate.addStatement(getHttpMethodParamsStatement(srcComponent.getTypeName(), params, false));
													srcUpdate.addStatement("result = " + getHttpMethodCallStatement(baseURL, 
																													dstRes.getResourceHierarchy().toResourcePath(pathParams), 
																													srcResName, 
																													httpMethod));
												}
												srcUpdate.addThrow("JsonProcessingException");
											} else {
												// Inner-service
												String updateMethodName = null;
												if (JerseyCodeGenerator.generatesComponent(dst.getResourceHierarchy())) {
													updateMethodName = "updateFrom" + srcResourceName;
												} else {
													updateMethodName = "update" + dstResourceName + "From" + srcResourceName;
												}
												String callParams = "";
												String delimiter = "";
												// Values of path parameters.
												for (Expression pathParam: dstRes.getPathParams()) {
													if (pathParam instanceof Variable) {
														Variable pathVar = (Variable) pathParam;
														callParams += delimiter + pathVar.getName();
														delimiter = ", ";
													}
												}
												// Values of other parameters.
												for (Map.Entry<Type, Map.Entry<String, String>> paramEnt: params) {
													callParams += delimiter + paramEnt.getValue().getValue();
													delimiter = ", ";
												}
												if (srcComponent != dstComponent) {
													srcUpdate.addStatement("this." + JerseyCodeGenerator.toVariableName(dstResourceName) + "." + updateMethodName + "(" + callParams  + ");");
												} else {
													srcUpdate.addStatement("this." + updateMethodName + "(" + callParams  + ");");
												}
												if (update != null && update.getThrows() != null && update.getThrows().getExceptions().contains("JsonProcessingException")) {
													srcUpdate.addThrow("JsonProcessingException");
												}
											}
										}
									}
									for (MethodDeclaration srcInput: getInputMethods(srcComponent, src, model)) {
										List<Map.Entry<Type, Map.Entry<String, String>>> params = new ArrayList<>();
										ResourcePath dstRes = out.getResource();
										// Values of channel parameters.
										for (Selector selector: ch.getAllSelectors()) {
											if (selector.getExpression() instanceof Variable) {
												Variable selVar = (Variable) selector.getExpression();
												params.add(new AbstractMap.SimpleEntry<>(selVar.getType(), 
																							new AbstractMap.SimpleEntry<>(selVar.getName(), selVar.getName())));
											}
										}
										// Value of the source side (input side) resource.
										String srcFieldName = "this.value";
										if (!JerseyCodeGenerator.generatesComponent(src.getResourceHierarchy())) {
											srcFieldName = JerseyCodeGenerator.toVariableName(srcResourceName);
										}
										params.add(new AbstractMap.SimpleEntry<>(src.getResourceStateType(), 
																					new AbstractMap.SimpleEntry<>(JerseyCodeGenerator.toVariableName(srcResourceName), srcFieldName)));
										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.getLeafResourceName();
												Type refResourceType = ref.getResourceStateType();
												if (!referredSet.contains(ref)) {
													referredSet.add(ref);
													String[] sideEffects = new String[] {""};
													if (rc.isOutside()) {
														List<String> pathParams = new ArrayList<>();
														for (Expression pathExp: ref.getPathParams()) {
															pathParams.add("\" + " + pathExp.toImplementation(sideEffects) + " + \"");
														}															
														generatePullDataTransfer(srcInput, refResourceName, ref.getResourceHierarchy().toResourcePath(pathParams), refResourceType);
													} else {
														ResourcePath srcRes = in.getResource();
														if (!JerseyCodeGenerator.generatesComponent(srcRes.getResourceHierarchy())) {
															srcRes = srcRes.getParent();
														}
														Expression refGetter = JerseyCodeGenerator.pullAccessor.getDirectStateAccessorFor(ref, srcRes);
														String refExp = refGetter.toImplementation(sideEffects);
														String refTypeName = ref.getResourceStateType().getInterfaceTypeName();
														srcInput.addFirstStatement(sideEffects[0] + refTypeName + " " + ref.getLeafResourceName() + " = " + refExp + ";");														
													}
												}
												// Value of a reference side resource.
												params.add(new AbstractMap.SimpleEntry<>(refResourceType, new AbstractMap.SimpleEntry<>(refResourceName, refResourceName)));
											}									
										}
										if (outsideOutputResource || (in.getResource().getCommonPrefix(dstRes) == null && JerseyCodeGenerator.differentTreesAsDifferentServices)) {
											// Inter-services
											String[] sideEffects = new String[] {""};
											List<String> pathParams = new ArrayList<>();
											for (Expression pathExp: dstRes.getPathParams()) {
												pathParams.add("\" + " + pathExp.toImplementation(sideEffects) + " + \"");
											}
											String srcResName = JerseyCodeGenerator.toVariableName(srcResourceName);
											if (inDegree <= 1) {
												srcResName = null;
											}
											if (!chainedCalls.contains(srcInput)) {
												// First call to an update method in this method
												srcInput.addStatement(getHttpMethodParamsStatement(srcComponent.getTypeName(), params, true));
												srcInput.addStatement("String result = " + getHttpMethodCallStatement(baseURL, 
																														dstRes.getResourceHierarchy().toResourcePath(pathParams), 
																														srcResName, 
																														httpMethod));
												chainedCalls.add(srcInput);
											} else {
												// After the second time of call to update methods in this method
												srcInput.addStatement(getHttpMethodParamsStatement(srcComponent.getTypeName(), params, false));
												srcInput.addStatement("result = " + getHttpMethodCallStatement(baseURL, 
																												dstRes.getResourceHierarchy().toResourcePath(pathParams), 
																												srcResName, 
																												httpMethod));
											}
											srcInput.addThrow("JsonProcessingException");
										} else {
											// Inner-service
											String updateMethodName = null;
											if (JerseyCodeGenerator.generatesComponent(dst.getResourceHierarchy())) {
												updateMethodName = "updateFrom" + srcResourceName;
											} else {
												updateMethodName = "update" + dstResourceName + "From" + srcResourceName;
											}
											String callParams = "";
											String delimiter = "";
											// Values of path parameters.
											for (Expression pathParam: dstRes.getPathParams()) {
												if (pathParam instanceof Variable) {
													Variable pathVar = (Variable) pathParam;
													callParams += delimiter + pathVar.getName();
													delimiter = ", ";
												}
											}
											// Values of other parameters.
											for (Map.Entry<Type, Map.Entry<String, String>> paramEnt: params) {
												callParams += delimiter + paramEnt.getValue().getValue();
												delimiter = ", ";
											}
											if (srcComponent != dstComponent) {
												srcInput.addStatement("this." + JerseyCodeGenerator.toVariableName(dstResourceName) + "." + updateMethodName + "(" + callParams  + ");");
											} else {
												srcInput.addStatement("this." + updateMethodName + "(" + callParams  + ");");
											}
											if (update != null && update.getThrows() != null && update.getThrows().getExceptions().contains("JsonProcessingException")) {
												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).getKey();		// 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 rc: ch.getReferenceChannelMembers()) {
											ResourcePath refRes = rc.getResource();
											String refResourceName = refRes.getLeafResourceName();
											Type refResourceType = refRes.getResourceStateType();
											if (rc.isOutside()) {
												List<String> pathParams = new ArrayList<>();
												for (Expression pathExp: refRes.getPathParams()) {
													pathParams.add("\" + " + pathExp.toImplementation(sideEffects) + " + \"");
												}
												generatePullDataTransfer(getter, refResourceName, refRes.getResourceHierarchy().toResourcePath(pathParams), refResourceType);
											} else {
												ResourcePath dstRes = out.getResource();
												if (!JerseyCodeGenerator.generatesComponent(dstRes.getResourceHierarchy())) {
													dstRes = dstRes.getParent();
												}
												Expression refGetter = JerseyCodeGenerator.pullAccessor.getDirectStateAccessorFor(refRes, dstRes);
												String refExp = refGetter.toImplementation(sideEffects);
												String refTypeName = refResourceType.getInterfaceTypeName();
												getter.addFirstStatement(sideEffects[0] + refTypeName + " " + refResourceName + " = " + refExp + ";");
											}
										}
										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.getInSideResource(ch)) == null) {
												Type srcResourceType = src2.getResourceStateType();
												List<String> pathParams = new ArrayList<>();
												for (Expression pathExp: src2.getPathParams()) {
													pathParams.add("\" + " + pathExp.toImplementation(sideEffects) + " + \"");
												}
												generatePullDataTransfer(getter, src2.getLeafResourceName(), src2.getResourceHierarchy().toResourcePath(pathParams), srcResourceType);
											}
										}
									}
									// get src resource state by pull data transfer.
									if (src.getNumberOfParameters() == 0 && src.getOutSideResource(ch).getCommonPrefix(dst.getInSideResource(ch)) == 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()) {
					DataTransferChannel ch = entry.getKey();
					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, ch.getOutputChannelMembers().size());
						} 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, ch.getOutputChannelMembers().size());
							}
						}
						if (input != null) {
							// In each resource
							Set<ResourcePath> referredSet = referredResources.get(input);
							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(input, referredSet);
								}
								if (!out.getResource().equals(ref)) {
									String refResourceName = ref.getLeafResourceName();
									Type refResourceType = ref.getResourceStateType();
									if (!referredSet.contains(ref)) {
										referredSet.add(ref);
										String[] sideEffects = new String[] {""};
										if (rc.isOutside()) {
											List<String> pathParams = new ArrayList<>();
											for (Expression pathExp: ref.getPathParams()) {
												pathParams.add("\" + " + pathExp.toImplementation(sideEffects) + " + \"");
											}
											generatePullDataTransfer(input, refResourceName, ref.getResourceHierarchy().toResourcePath(pathParams), refResourceType);											
										} else {
											ResourcePath dstRes = out.getResource();
											if (!JerseyCodeGenerator.generatesComponent(dstRes.getResourceHierarchy())) {
												dstRes = dstRes.getParent();
											}
											Expression refGetter = JerseyCodeGenerator.pullAccessor.getDirectStateAccessorFor(ref, dstRes);
											String refExp = refGetter.toImplementation(sideEffects);
											String refTypeName = refResourceType.getInterfaceTypeName();
											input.addFirstStatement(sideEffects[0] + refTypeName + " " + refResourceName + " = " + refExp + ";");
										}
									}
								}
							}
							Expression updateExp = ch.deriveUpdateExpressionOf(out, JerseyCodeGenerator.refAccessor).getKey();
							// 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.addStatement(updateStatement);
								}
							} else {
								String updateStatement = "";
								if (sideEffects[0] != null) {
									updateStatement = sideEffects[0];	
									updateStatement = updateStatement.replace(".value", "." + JerseyCodeGenerator.toVariableName(JerseyCodeGenerator.getComponentName(resource)));
								}
								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 += 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 += sideEffects2[0];
								} else if (!(updateExp instanceof Term && ((Term) updateExp).getSymbol().isImplWithSideEffect())) {
									updateStatement += "this." + JerseyCodeGenerator.toVariableName(JerseyCodeGenerator.getComponentName(resource)) + " = " + newState + ";";
								}
								if (updateStatement != null && (input.getBody() == null || !input.getBody().getStatements().contains(updateStatement))) {
									input.addStatement(updateStatement);
								}
							}
							
							if (out.getResource().getParent() != null && out.getResource().getParent().getParent() != null) {
								// In the root resource
								Expression message = out.getStateTransition().getMessageExpression();
								String inputAccessorName = input.getName();
								if (message instanceof Term) {
									inputAccessorName = ((Term) message).getSymbol().getImplName();
								} else if (message instanceof Variable) {
									inputAccessorName = ((Variable) message).getName();
								}
								MethodDeclaration inputAccessor = getMethod(componentMap.get(JerseyCodeGenerator.getComponentName(resource.getRoot())), inputAccessorName);
								if (inputAccessor != null) {
									// The expression of the receiver (resource) of the input method.
									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});
									// Values of channel parameters.
									for (Selector selector: ch.getAllSelectors()) {
										if (selector.getExpression() instanceof Variable) {
											Variable selVar = (Variable) selector.getExpression();
											args += delimiter + selVar.getName();
											delimiter = ", ";
										}
									}
									// Values of message parameters.
									if (message instanceof Term) {
										for (Variable mesVar: message.getVariables().values()) {
											args += delimiter + mesVar.getName();
											delimiter = ", ";
										}
									}
									inputAccessor.addStatement(resourceAccess + "." + input.getName() + "(" + args + ");");
									if (input != null && input.getThrows() != null && input.getThrows().getExceptions().contains("JsonProcessingException")) {
										inputAccessor.addThrow("JsonProcessingException");
									}
								}
							}
						}
					}
				}
			}
		} 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) {
						if (param instanceof Term) {
							if (((Term) param).getType() == null) {
								((Term) param).setType(var.getType());
							}
						} else if (param instanceof Variable) {
							if (((Variable) param).getType() == null) {
								((Variable) param).setType(var.getType());
							}
						}
						constructorInvocation = constructorInvocation + delimiter + param.toImplementation(null);
					} 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, String dstResName, String srcResName) {
		for (MethodDeclaration m: component.getMethods()) {
			if (dstResName == null) {
				if (m.getName().equals("updateFrom" + srcResName)) return m;
			} else {
				if (m.getName().equals("update" + dstResName + "From" + srcResName)) 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, String resName) {
		List<MethodDeclaration> updates = new ArrayList<>();
		for (MethodDeclaration m: component.getMethods()) {
			if (resName == null) {
				if (m.getName().startsWith("updateFrom")) {
					updates.add(m);
				}
			} else {
				if (m.getName().startsWith("update" + resName + "From")) {
					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.getInputChannels()) {
			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.getInputChannels()) {
			DataTransferChannel channel = (DataTransferChannel) c;
			// I/O channel
			for (ChannelMember out: channel.getOutputChannelMembers()) {
				if (resource.getInSideResources().contains(out.getResource())) {
					MethodDeclaration input = getInputMethod(component, out, channel.getOutputChannelMembers().size());
					inputs.add(input);
				}
			}
		}
		return inputs;
	}

	private static MethodDeclaration getInputMethod(TypeDeclaration component, ChannelMember cm, int outNumber) {
		String inputMethodName = null;
		if (cm.getStateTransition().getMessageExpression() instanceof Term) {
			Term message = (Term) cm.getStateTransition().getMessageExpression();
			inputMethodName = message.getSymbol().getImplName();
		} else if (cm.getStateTransition().getMessageExpression() instanceof Variable) {
			Variable message = (Variable) cm.getStateTransition().getMessageExpression();
			inputMethodName = message.getName();
		}
		if (outNumber > 1) {
			inputMethodName += "For" + JerseyCodeGenerator.getComponentName(cm.getResource().getResourceHierarchy());
		}
		MethodDeclaration input = getMethod(component, inputMethodName);
		return input;
	}

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