package generators;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import code.ast.Annotation;
import code.ast.Block;
import code.ast.CompilationUnit;
import code.ast.FieldDeclaration;
import code.ast.ImportDeclaration;
import code.ast.MethodDeclaration;
import code.ast.TypeDeclaration;
import code.ast.VariableDeclaration;
import models.Edge;
import models.Node;
import models.algebra.Expression;
import models.algebra.Field;
import models.algebra.Parameter;
import models.algebra.Position;
import models.algebra.Symbol;
import models.algebra.Term;
import models.algebra.Type;
import models.algebra.Variable;
import models.dataConstraintModel.Channel;
import models.dataConstraintModel.ChannelMember;
import models.dataConstraintModel.DataConstraintModel;
import models.dataConstraintModel.ResourceHierarchy;
import models.dataConstraintModel.ResourcePath;
import models.dataConstraintModel.Selector;
import models.dataFlowModel.DataTransferModel;
import models.dataFlowModel.DataTransferChannel;
import models.dataFlowModel.DataTransferChannel.IResourceStateAccessor;
import models.dataFlowModel.PushPullAttribute;
import models.dataFlowModel.PushPullValue;
import models.dataFlowModel.DataFlowEdge;
import models.dataFlowModel.DataFlowGraph;
import models.dataFlowModel.ResourceNode;
import models.dataFlowModel.ChannelNode;
import models.dataFlowModel.StoreAttribute;

/**
 * Generator for Jersey prototypes
 * 
 * @author Nitta
 *
 */
public class JerseyCodeGenerator {
	public static final Type typeVoid = new Type("Void", "void");
	public static final Type typeClient = new Type("Client", "Client");
	public static boolean differentTreesAsDifferentServices = true;
	private static String defaultMainTypeName = "Main";
	static String mainTypeName = defaultMainTypeName;

	public static String getMainTypeName() {
		return mainTypeName;
	}

	public static void setMainTypeName(String mainTypeName) {
		JerseyCodeGenerator.mainTypeName = mainTypeName;
	}

	public static void resetMainTypeName() {
		JerseyCodeGenerator.mainTypeName = defaultMainTypeName;
	}

	public static String getComponentName(ResourceHierarchy res) {
		String name = res.getResourceName();
		if (res.getNumParameters() > 0) {
			if (name.length() > 3 && name.endsWith("ies")) {
				name = name.substring(0, name.length() - 3) + "y";
			} else if (name.length() > 1 && name.endsWith("s")) {
				name = name.substring(0, name.length() - 1);
			} else {
				name += "Element";
			}
		}
		return name.substring(0, 1).toUpperCase() + name.substring(1);
	}
	
	public static String toVariableName(String name) {
		return name.substring(0, 1).toLowerCase() + name.substring(1);
	}
	
	public static Type getImplStateType(ResourceHierarchy res) {
		Set<ResourceHierarchy> children = res.getChildren();
		if (children == null || children.size() == 0) {
			// leaf resource.
			return res.getResourceStateType();
		} else {
			ResourceHierarchy child = children.iterator().next();
			if (children.size() == 1 && child.getNumParameters() > 0) {
				// map or list.
				if (DataConstraintModel.typeList.isAncestorOf(res.getResourceStateType())) {
					// list.
					if (generatesComponent(child)) {
						return new Type("List", "ArrayList<>", "List<" + getComponentName(child) + ">", DataConstraintModel.typeList);
					} else {
						return new Type("List", "ArrayList<>", "List<" + getImplStateType(child).getImplementationTypeName() + ">", DataConstraintModel.typeList);
					}
				} else if (DataConstraintModel.typeMap.isAncestorOf(res.getResourceStateType())) {
					// map.
					if (generatesComponent(child)) {
						return new Type("Map", "HashMap<>", "Map<String, " + getComponentName(child) + ">", DataConstraintModel.typeMap);
					} else {
						return new Type("Map", "HashMap<>", "Map<String, " + getImplStateType(child).getImplementationTypeName() + ">", DataConstraintModel.typeMap);
					}
				}
				return null;
			} else {
				// class
				return res.getResourceStateType();
			}
		}
	}

	public static boolean generatesComponent(ResourceHierarchy res) {
		return res.getParent() == null || !(res.getChildren() == null || res.getChildren().size() == 0);
//		Type resType = res.getResourceStateType();
//		return DataConstraintModel.typeJson.isAncestorOf(resType) || DataConstraintModel.typeList.isAncestorOf(resType) || DataConstraintModel.typeMap.isAncestorOf(resType);
	}

	static public ArrayList<CompilationUnit> doGenerate(DataFlowGraph graph, DataTransferModel model) {
		ArrayList<CompilationUnit> codes = new ArrayList<>();
//		ArrayList<ResourceNode> resources = StoreResourceCheck(graph);
		Collection<ResourceNode> resources = graph.getResourceNodes();
		Map<ResourceHierarchy, TypeDeclaration> resourceComponents = new HashMap<>();
		Map<ResourceHierarchy, MethodDeclaration> resourceConstructors = new HashMap<>();
		List<Map.Entry<ResourceHierarchy, MethodDeclaration>> getters = new ArrayList<>();
		Map<ResourceHierarchy, Map<String, MethodDeclaration>> updates = new HashMap<>();
		Map<ResourceHierarchy, Map<String, MethodDeclaration>> inputs = new HashMap<>();
		List<Map.Entry<ResourceHierarchy, FieldDeclaration>> fields = new ArrayList<>();
		Map<ResourceHierarchy, MethodDeclaration> getterAccessors = new HashMap<>();
		Map<ResourceHierarchy, MethodDeclaration> inputAccessors = new HashMap<>();
		Map<ResourceHierarchy, Map<String, VariableDeclaration>> constructorParams = new HashMap<>();
		Map<Channel, ChannelMember> priorMemberForInputChannel = new HashMap<>();

		// For each resource node.
		for (Node n : resources) {
			ResourceNode resourceNode = (ResourceNode) n;
			TypeDeclaration component = null;
			if (generatesComponent(resourceNode.getResourceHierarchy())) {
				String resourceName = getComponentName(resourceNode.getResourceHierarchy());
	
				component = resourceComponents.get(resourceNode.getResourceHierarchy());
				if (component == null) {
					// Add compilation unit for each resource.
					component = new TypeDeclaration(resourceName);
					if (resourceNode.getResourceHierarchy().getParent() == null) {
						// For a root node.
						component.addAnnotation(new Annotation("Component"));
						component.addAnnotation(new Annotation("Path", "\"/" + resourceNode.getResourceName() + "\""));
					}
					resourceComponents.put(resourceNode.getResourceHierarchy(), component);
					
					CompilationUnit cu = new CompilationUnit(component);
					cu.addImport(new ImportDeclaration("java.util.*"));
					if (resourceNode.getResourceHierarchy().getParent() == null) {
						// For a root node.
						cu.addImport(new ImportDeclaration("javax.ws.rs.*"));
						cu.addImport(new ImportDeclaration("javax.ws.rs.client.*"));
						cu.addImport(new ImportDeclaration("javax.ws.rs.core.*"));
						cu.addImport(new ImportDeclaration("org.springframework.stereotype.Component"));
						cu.addImport(new ImportDeclaration("com.fasterxml.jackson.databind.ObjectMapper"));
						cu.addImport(new ImportDeclaration("com.fasterxml.jackson.core.JsonProcessingException"));
					}
					codes.add(cu);
					
					// Declare the field to store the state in the type of each resource.
					if (((StoreAttribute) resourceNode.getAttribute()).isStored()) {
						ResourceHierarchy res = resourceNode.getResourceHierarchy();
						Set<ResourceHierarchy> children = res.getChildren();
						if (children == null || children.size() == 0) {
							// leaf resource.
							Type fieldType = getImplStateType(res);
							component.addField(new FieldDeclaration(fieldType, "value", getInitializer(res)));
							Map<String, VariableDeclaration> nameToParam = constructorParams.get(res);
							if (nameToParam == null) {
								nameToParam = new HashMap<>();
								constructorParams.put(resourceNode.getResourceHierarchy(), nameToParam);
							}
							String varName = toVariableName(resourceName);
							if (nameToParam.get(varName) == null) {
								nameToParam.put(varName, new VariableDeclaration(fieldType, varName));
							}
						} else {
							ResourceHierarchy child = children.iterator().next();
							if (children.size() == 1 && child.getNumParameters() > 0) {
								// map or list.
								component.addField(new FieldDeclaration(getImplStateType(res), "value", getInitializer(res)));
							} else {
								// class
								for (ResourceHierarchy c: children) {
									String childTypeName = getComponentName(c);
									Type childType = null;
									if (generatesComponent(c)) {
										// The child has a component.
										childType = new Type(childTypeName, childTypeName);
										String fieldName = toVariableName(childTypeName);
										component.addField(new FieldDeclaration(childType, fieldName, getInitializer(res)));							
									}
								}
							}
						}
					}
					
					// Declare the getter method to obtain the resource state in the component of each resource.
					MethodDeclaration stateGetter = new MethodDeclaration("getValue", getImplStateType(resourceNode.getResourceHierarchy()));
					if (resourceNode.getResourceHierarchy().getParent() == null) {
						// Since this getter is also an accessor.
						stateGetter.addAnnotation(new Annotation("Produces", "MediaType.APPLICATION_JSON"));
						stateGetter.addAnnotation(new Annotation("GET"));
					}
					component.addMethod(stateGetter);
					
					// Declare the getter methods to obtain the children resources.
					Set<ResourceHierarchy> children = new HashSet<>();
					for (ResourceNode child: resourceNode.getChildren()) {
						if (generatesComponent(child.getResourceHierarchy())) {
							// The child generates a component.
							if (!children.contains(child.getResourceHierarchy())) {
								children.add(child.getResourceHierarchy());
								List<VariableDeclaration> pathParams = new ArrayList<>();
								int v = 1;
								for (Expression pathParam: child.getPrimaryResourcePath().getPathParams()) {
									if (pathParam instanceof Variable) {
										Variable var = (Variable) pathParam;
										pathParams.add(new VariableDeclaration(var.getType(), var.getName()));
									} else if (pathParam instanceof Term) {
										Term var = (Term) pathParam;
										pathParams.add(new VariableDeclaration(var.getType(), "v" + v));
									}
									v++;
								}
								String childCompName = getComponentName(child.getResourceHierarchy());
								Type childType = new Type(childCompName, childCompName);
								MethodDeclaration childGetter = null;
								if (pathParams.size() == 0) {
									childGetter = new MethodDeclaration("get" + childCompName, childType);
								} else {
									childGetter = new MethodDeclaration("get" + childCompName, false, childType, pathParams);
								}
								component.addMethod(childGetter);
							}
						}
					}
				}
				
//				// Declare a client field to connect to the source resource of reference transfer.
//				if (!bDeclareClientField) {
//					for (ChannelGenerator cg : model.getChannelGenerators()) {
//						DataflowChannelGenerator dcg = ((DataflowChannelGenerator) cg);
//						for (ChannelMember cm : dcg.getOutputChannelMembers()) {
//							if (cm.getIdentifierTemplate().getResourceName().equals(type.getTypeName().toLowerCase())) {
//								if (dcg.getReferenceChannelMembers().size() > 0) {
//									// If there exists one or more reference channel member.
//									type.addField(new FieldDeclaration(typeClient, "client", "ClientBuilder.newClient()"));
//									bDeclareClientField = true;
//									break;
//								}
//							}
//						}
//						if (bDeclareClientField) break;
//					}
//				}
			}
			
			// Declare the state field and reference fields in the parent component.
			boolean bDeclareClientField = false;
			if (component == null) {
				// Declare reference fields for push/pull data transfer.
				boolean noPullTransfer = true;
				for (Edge resToCh : resourceNode.getOutEdges()) {
					DataFlowEdge re = (DataFlowEdge) resToCh;
					DataTransferChannel ch = ((ChannelNode) re.getDestination()).getChannel();
					for (Edge chToRes: re.getDestination().getOutEdges()) {
						ResourceHierarchy dstRes = ((ResourceNode) chToRes.getDestination()).getResourceHierarchy();
						// Check if the output resource is outside of the channel scope.
						boolean outsideOutputResource = false;
						for (ChannelMember cm: ch.getOutputChannelMembers()) {
							if (((ResourceNode) chToRes.getDestination()).getInSideResources().contains(cm.getResource()) && cm.isOutside()) {
								outsideOutputResource = true;	// Regarded as push transfer.
								break;
							}
						}
						if (outsideOutputResource) {
							// Declare a field in the parent component to refer to the destination resource of push transfer.
							if (!generatesComponent(dstRes)) {
								dstRes = dstRes.getParent();
							}
							String dstResName = getComponentName(dstRes);
//							if (resourceNode.getOutSideResource().getCommonPrefix(dstRes) == null && differentTreesAsDifferentServices) {
								// Inter-service
								if (!bDeclareClientField) {
									// Declare a client field to connect to the destination resource of push transfer.
									FieldDeclaration clientField = new FieldDeclaration(typeClient, "client", "ClientBuilder.newClient()");
									fields.add(new AbstractMap.SimpleEntry<>(resourceNode.getParent().getResourceHierarchy(), clientField));
									bDeclareClientField = true;
								}
//							} else {
//								// Inner-service
//								// Declare a field to directly refer to the destination resource of push transfer.
//								if (resourceNode.getParent().getResourceHierarchy() != dstRes.getResourceHierarchy()) {
//									FieldDeclaration refFieldForPush = new FieldDeclaration(new Type(dstResName, dstResName), toVariableName(dstResName));
//									fields.add(new AbstractMap.SimpleEntry<>(resourceNode.getParent().getResourceHierarchy(), refFieldForPush));
//								}
//							}
						}
					}
				}
				for (Edge chToRes : resourceNode.getInEdges()) {
					for (Edge resToCh: chToRes.getSource().getInEdges()) {
						DataFlowEdge re = (DataFlowEdge) resToCh;
						DataTransferChannel ch = ((ChannelNode) re.getDestination()).getChannel();
						ResourcePath srcRes = ((ResourceNode) re.getSource()).getOutSideResource(ch);
						// Check if the input resource is outside of the channel scope.
						boolean outsideInputResource = false;
						for (ChannelMember cm: ch.getInputChannelMembers()) {
							if (cm.getResource().equals(srcRes) && cm.isOutside()) {
								outsideInputResource = true;	// Regarded as pull transfer.
								break;
							}
						}
						if (outsideInputResource) {
							// Declare a field in the parent component to refer to the source resource of pull transfer.
							if (!generatesComponent(srcRes.getResourceHierarchy())) {
								srcRes = srcRes.getParent();
							}
							String srcResName = getComponentName(srcRes.getResourceHierarchy());
//							if (resourceNode.getOutSideResource().getCommonPrefix(srcRes) == null && differentTreesAsDifferentServices) {
								// Inter-service
								if (!bDeclareClientField) {
									// Declare a client field to connect to the source resource of pull transfer.
									FieldDeclaration clientField = new FieldDeclaration(typeClient, "client", "ClientBuilder.newClient()");
									fields.add(new AbstractMap.SimpleEntry<>(resourceNode.getParent().getResourceHierarchy(), clientField));
									bDeclareClientField = true;
								}
//							} else {
//								// Inner-service								
//								// Declare a field to directly refer to the source resource of pull transfer.
//								if (resourceNode.getParent().getResourceHierarchy() != srcRes.getResourceHierarchy()) {
//									FieldDeclaration refFieldForPull = new FieldDeclaration(new Type(srcResName, srcResName), toVariableName(srcResName));
//									fields.add(new AbstractMap.SimpleEntry<>(resourceNode.getParent().getResourceHierarchy(), refFieldForPull));
//								}
//							}
							noPullTransfer = false;
						}
					}
				}
				// Declare the state field in the parent component.
				ResourceHierarchy res = resourceNode.getResourceHierarchy();
				if (((StoreAttribute) resourceNode.getAttribute()).isStored() && noPullTransfer && res.getNumParameters() == 0) {
					String resName = getComponentName(res);
					FieldDeclaration stateField = new FieldDeclaration(res.getResourceStateType(), toVariableName(resName));
					fields.add(new AbstractMap.SimpleEntry<>(resourceNode.getParent().getResourceHierarchy(), stateField));

					
					Map<String, VariableDeclaration> nameToParam = constructorParams.get(resourceNode.getParent().getResourceHierarchy());
					if (nameToParam == null) {
						nameToParam = new HashMap<>();
						constructorParams.put(resourceNode.getParent().getResourceHierarchy(), nameToParam);
					}
					String varName = toVariableName(resName);
					if (nameToParam.get(varName) == null) {
						nameToParam.put(varName, new VariableDeclaration(res.getResourceStateType(), varName));
					}
				}
			}
			
			// Declare the getter method to obtain the resource state in the parent component.
			if (component == null) {
				// No component is created for this resource.			
				String getterName = "get" + getComponentName(resourceNode.getResourceHierarchy());
				boolean bExists = false;
				for (Map.Entry<ResourceHierarchy, MethodDeclaration> entry: getters) {
					if (entry.getKey() == resourceNode.getParent().getResourceHierarchy() && entry.getValue().getName().equals(getterName)) {
						bExists = true;
						break;
					}
				}
				if (!bExists) {
					List<VariableDeclaration> pathParams = new ArrayList<>();
					int v = 1;
					for (Selector pathParam: resourceNode.getSelectors()) {
						if (pathParam.getExpression() instanceof Variable) {
							Variable var = (Variable) pathParam.getExpression();
							pathParams.add(new VariableDeclaration(var.getType(), var.getName()));
						} else if (pathParam.getExpression() instanceof Term) {
							Term var = (Term) pathParam.getExpression();
							pathParams.add(new VariableDeclaration(var.getType(), "v" + v));
						}
						v++;
					}
					Type resType = getImplStateType(resourceNode.getResourceHierarchy());
					MethodDeclaration stateGetter = null;
					if (pathParams.size() == 0) {
						stateGetter = new MethodDeclaration(getterName, resType);
					} else {
						stateGetter = new MethodDeclaration(getterName, false, resType, pathParams);
					}
					getters.add(new AbstractMap.SimpleEntry<>(resourceNode.getParent().getResourceHierarchy(), stateGetter));
				}
			}
			
			// Declare the getter accessor in the root resource.
			if (resourceNode.getResourceHierarchy().getParent() != null) {
				// For a non-root resource
				MethodDeclaration getterAccessor = null;
				List<VariableDeclaration> mainGetterParams = new ArrayList<>();
				String resourcePath = getGetterResourcePathAndPathParams(resourceNode.getPrimaryResourcePath(), mainGetterParams);
				if (resourcePath.indexOf('/') > 0) {
					resourcePath = resourcePath.substring(resourcePath.indexOf('/'));
				} else {
					resourcePath = "";
				}
				if (mainGetterParams.size() > 0) {
					getterAccessor = new MethodDeclaration("get" + getComponentName(resourceNode.getResourceHierarchy()) + "Value",
														false,
														getImplStateType(resourceNode.getResourceHierarchy()),
														mainGetterParams);
				} else {
					getterAccessor = new MethodDeclaration("get" + getComponentName(resourceNode.getResourceHierarchy()) + "Value",
														getImplStateType(resourceNode.getResourceHierarchy()));
				}
				getterAccessor.setBody(new Block());
				ResourcePath resPath = resourceNode.getPrimaryResourcePath();
				Expression getState = JerseyCodeGenerator.pullAccessor.getDirectStateAccessorFor(resPath, resPath.getRoot());
				getterAccessor.getBody().addStatement("return " + getState.toImplementation(new String[] {null}) + ";");
				
				getterAccessor.addAnnotation(new Annotation("Produces", "MediaType.APPLICATION_JSON"));
				getterAccessor.addAnnotation(new Annotation("GET"));
				if (resourcePath.length() > 0) {
					getterAccessor.addAnnotation(new Annotation("Path", "\"" + resourcePath + "\""));
				}
				getterAccessors.put(resourceNode.getResourceHierarchy(), getterAccessor);
			}
			
			// Declare a client field and update methods from other resources.
			for (Edge resToCh: resourceNode.getOutEdges()) {
				DataFlowEdge re = (DataFlowEdge) resToCh;
				DataTransferChannel ch = ((ChannelNode) re.getDestination()).getChannel();
				// Check if the input resource is outside of the channel scope.
				boolean outsideInputResource = false;
				for (ChannelMember cm: ch.getInputChannelMembers()) {
					if (cm.getResource().equals(resourceNode.getOutSideResource(ch)) && cm.isOutside()) {
						outsideInputResource = true;	// Regarded as pull transfer.
						break;
					}
				}
				for (ChannelMember cm: ch.getOutputChannelMembers()) {
					ResourcePath dstRes = cm.getResource();
					// Check if the output resource is outside of the channel scope.
					boolean outsideOutputResource = cm.isOutside();
					if (!bDeclareClientField && ((((PushPullAttribute) re.getAttribute()).getOptions().get(0) == PushPullValue.PUSH && !outsideInputResource) || outsideOutputResource)) {
						// For push transfer.
						if (!generatesComponent(dstRes.getResourceHierarchy())) {
							dstRes = dstRes.getParent();
						}
						String dstResName = getComponentName(dstRes.getResourceHierarchy());
						if (outsideOutputResource || (resourceNode.getOutSideResource(ch).getCommonPrefix(dstRes) == null && differentTreesAsDifferentServices)) {
							// Inter-service
							if (!bDeclareClientField) {
								// Declare a client field to connect to the destination resource of push transfer.
								FieldDeclaration clientField = new FieldDeclaration(typeClient, "client", "ClientBuilder.newClient()");
								if (component != null) {
									// A component is created for this resource.
									component.addField(clientField);
								} else {
									// No component is created for this resource.
									fields.add(new AbstractMap.SimpleEntry<>(resourceNode.getParent().getResourceHierarchy(), clientField));
								}
								bDeclareClientField = true;
							}
						} else {
							// Inner-service
							// Declare a field to directly refer to the destination resource of push transfer.
							FieldDeclaration dstRefField = new FieldDeclaration(new Type(dstResName, dstResName), toVariableName(dstResName));
							if (component != null) {
								// A component is created for this resource.
								if (resourceNode.getResourceHierarchy() != dstRes.getResourceHierarchy()) {
									component.addField(dstRefField);
								}
							} else {
								// No component is created for this resource.
								if (resourceNode.getParent().getResourceHierarchy() != dstRes.getResourceHierarchy()) {
									fields.add(new AbstractMap.SimpleEntry<>(resourceNode.getParent().getResourceHierarchy(), dstRefField));
								}
							}
						}
					}
				}
			}
			for (Edge chToRes : resourceNode.getInEdges()) {
				for (Edge resToCh: chToRes.getSource().getInEdges()) {
					DataFlowEdge re = (DataFlowEdge) resToCh;
					DataTransferChannel ch = ((ChannelNode) re.getDestination()).getChannel();
					ResourcePath srcRes = ((ResourceNode) re.getSource()).getOutSideResource(ch);
					// Check if the input resource is outside of the channel scope.
					boolean outsideInputResource = false;
					for (ChannelMember cm: ch.getInputChannelMembers()) {
						if (cm.getResource().equals(srcRes) && cm.isOutside()) {
							outsideInputResource = true;	// Regarded as pull transfer.
							break;
						}
					}
					// Check if the output resource is outside of the channel scope.
					boolean outsideOutputResource = false;
					ChannelMember out = null;
					for (ChannelMember cm: ch.getOutputChannelMembers()) {
						if (resourceNode.getInSideResources().contains(cm.getResource())) {
							out = cm;
							if (cm.isOutside()) {
								outsideOutputResource = true;	// Regarded as push transfer.
								break;
							}
						}
					}
					String srcResName = getComponentName(srcRes.getResourceHierarchy());
					Type srcType = srcRes.getResourceStateType();
					if (!generatesComponent(srcRes.getResourceHierarchy())) {
						srcRes = srcRes.getParent();
					}
					if ((((PushPullAttribute) re.getAttribute()).getOptions().get(0) != PushPullValue.PUSH && !outsideOutputResource) || outsideInputResource) {
						// For pull transfer.
						if (outsideInputResource || (resourceNode.getInSideResource(ch).getCommonPrefix(srcRes) == null && differentTreesAsDifferentServices)) {
							// Inter-service
							if (!bDeclareClientField) {
								// Declare a client field to connect to the source resource of pull transfer.
								FieldDeclaration clientField = new FieldDeclaration(typeClient, "client", "ClientBuilder.newClient()");
								if (component != null) {
									// A component is created for this resource.
									component.addField(clientField);
								} else {
									// No component is created for this resource.
									fields.add(new AbstractMap.SimpleEntry<>(resourceNode.getParent().getResourceHierarchy(), clientField));
								}
								bDeclareClientField = true;
							}
						} else {
							// Inner-service
							// Declare a field to directly refer to the source resource of pull transfer.
							FieldDeclaration srcRefField = new FieldDeclaration(new Type(srcResName, srcResName), toVariableName(srcResName));
							if (component != null) {
								// A component is created for this resource.
								if (resourceNode.getResourceHierarchy() != srcRes.getResourceHierarchy()) {
									component.addField(srcRefField);
								}
							} else {
								// No component is created for this resource.
								if (resourceNode.getParent().getResourceHierarchy() != srcRes.getResourceHierarchy()) {
									fields.add(new AbstractMap.SimpleEntry<>(resourceNode.getParent().getResourceHierarchy(), srcRefField));
								}
							}
						}
					} else {
						// For push transfer.
						boolean hasRestAPI = false;
						boolean isRestAPI = false;
						if (outsideOutputResource || (resourceNode.getInSideResource(ch).getCommonPrefix(srcRes) == null && differentTreesAsDifferentServices)) {
							// Inter-service
							hasRestAPI = true;
							if (resourceNode.getParent() == null) {
								// A root resource
								isRestAPI = true;
							}
						}
						// Declare an update method in the type of the destination resource.
						ArrayList<VariableDeclaration> params = new ArrayList<>();
						getUpdateResourcePathAndPathParams(out.getResource(), params, isRestAPI);		// Path parameters to identify the self resource.
						for (Selector selector: ch.getAllSelectors()) {
							if (selector.getExpression() instanceof Variable) {
								Variable selVar = (Variable) selector.getExpression();
								VariableDeclaration chParam = new VariableDeclaration(selVar.getType(), selVar.getName());
								if (isRestAPI) chParam.addAnnotation(new Annotation("FormParam", "\"" + selVar.getName() + "\""));
								params.add(chParam);	// A channel parameter to specify the context of the collaboration.
							}
						}
						String srcName = toVariableName(srcResName);
						VariableDeclaration param = new VariableDeclaration(srcType, srcName);
						if (isRestAPI) param.addAnnotation(new Annotation("FormParam", "\"" + srcName + "\""));
						params.add(param);	// The state of the source resource to carry the data-flow.
						for (ResourcePath refRes: ch.getReferenceResources()) {
							if (!refRes.equals(resourceNode.getInSideResource(ch))) {
								param = new VariableDeclaration(refRes.getResourceStateType(), refRes.getLeafResourceName());
								if (isRestAPI) param.addAnnotation(new Annotation("FormParam", "\"" + refRes.getLeafResourceName() + "\""));
								params.add(param);						
							}
						}
						MethodDeclaration update = null;
						if (component != null) {
							// A component is created for this resource.
							update = new MethodDeclaration("updateFrom" + srcResName, false, typeVoid, params);
						} else {
							// No component is created for this resource.
							String resourceName = getComponentName(resourceNode.getResourceHierarchy());
							update = new MethodDeclaration("update" + resourceName + "From" + srcResName, false, typeVoid, params);
						}
						// Determine whether the update method is put or post.
						boolean isPut = false;
						for (ChannelMember cm: ch.getOutputChannelMembers()) {
							if (resourceNode.getInSideResources().contains(cm.getResource())) {
								if (cm.getStateTransition().isRightUnary()) {
									isPut = true;
								} else {
									isPut = false;
								}
							}
						}
						if (isRestAPI) {
							if (isPut) {
								update.addAnnotation(new Annotation("PUT"));
							} else {
								update.addAnnotation(new Annotation("POST"));
							}
						}
						// 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())) {
							// Declare a field to cache the state of the source resource in the type of the destination resource.
							ResourceHierarchy cacheRes = ((ResourceNode) re.getSource()).getResourceHierarchy();
							FieldDeclaration cacheField = new FieldDeclaration(cacheRes.getResourceStateType(), srcName, getInitializer(cacheRes));
							if (component != null) {
								// A component is created for this resource.
								component.addField(cacheField);
							} else {
								// No component is created for this resource.
								fields.add(new AbstractMap.SimpleEntry<>(resourceNode.getParent().getResourceHierarchy(), cacheField));
							}
							if (inDegree > 1) {
								 // For each source resource, a child resource is defined in the destination resource so that its state can be updated separately.
								if (isRestAPI) update.addAnnotation(new Annotation("Path", "\"/" + srcName + "\""));
							}
						}
						if (component != null) {
							// A component is created for this resource.
							component.addMethod(update);
						} else {
							// No component is created for this resource.
							String updateMethodName = update.getName();
							Map<String, MethodDeclaration> nameToMethod = updates.get(resourceNode.getParent().getResourceHierarchy());
							if (nameToMethod == null) {
								nameToMethod = new HashMap<>();
								updates.put(resourceNode.getParent().getResourceHierarchy(), nameToMethod);
							}
							if (nameToMethod.get(updateMethodName) == null) {
								nameToMethod.put(updateMethodName, update);
							}
						}
						if (hasRestAPI && !isRestAPI) {
							// Declare an update accessor method in the type of root resource. 
							String updateMethodName = update.getName();
							params = new ArrayList<>();
							String resourcePath = getUpdateResourcePathAndPathParams(out.getResource(), params, true);	// Path parameters to identify the self resource.
							for (Selector selector: ch.getAllSelectors()) {
								if (selector.getExpression() instanceof Variable) {
									Variable selVar = (Variable) selector.getExpression();
									VariableDeclaration chParam = new VariableDeclaration(selVar.getType(), selVar.getName());
									chParam.addAnnotation(new Annotation("FormParam", "\"" + selVar.getName() + "\""));
									params.add(chParam);	// A channel parameter to specify the context of the collaboration.
								}
							}
							param = new VariableDeclaration(srcType, srcName);
							param.addAnnotation(new Annotation("FormParam", "\"" + srcName + "\""));
							params.add(param);	// The state of the source resource to carry the data-flow.
							for (ResourcePath refRes: ch.getReferenceResources()) {
								if (!refRes.equals(resourceNode.getInSideResource(ch))) {
									param = new VariableDeclaration(refRes.getResourceStateType(), refRes.getLeafResourceName());
									param.addAnnotation(new Annotation("FormParam", "\"" + refRes.getLeafResourceName() + "\""));
									params.add(param);						
								}
							}
							MethodDeclaration updateAccessor = new MethodDeclaration(updateMethodName, false, typeVoid, params);
							if (isPut) {
								updateAccessor.addAnnotation(new Annotation("PUT"));
							} else {
								updateAccessor.addAnnotation(new Annotation("POST"));
							}
							if (inDegree > 1) {
								 // For each source resource, a child resource is defined in the destination resource so that its state can be updated separately.
								resourcePath += "/" + toVariableName(srcResName);
							}
							updateAccessor.addAnnotation(new Annotation("Path", "\"" + resourcePath + "\""));
							Map<String, MethodDeclaration> nameToMethod = updates.get(resourceNode.getResourceHierarchy().getRoot());
							if (nameToMethod == null) {
								nameToMethod = new HashMap<>();
								updates.put(resourceNode.getResourceHierarchy().getRoot(), nameToMethod);
							}
							if (nameToMethod.get(updateMethodName) == null) {
								nameToMethod.put(updateMethodName, updateAccessor);
							}
						}
					}
				}
			}
			
			// Declare the input method in each resource and the root resource.
			for (Channel ch : model.getInputChannels()) {
				for (ChannelMember cm : ((DataTransferChannel) ch).getOutputChannelMembers()) {
					if (!cm.isOutside()) {
						if (priorMemberForInputChannel.get(ch) == null) {
							priorMemberForInputChannel.put(ch, cm);		// The receiver of the input event when multiple output resources are defined for the channel.
						}
					}
				}
				for (ChannelMember cm : ((DataTransferChannel) ch).getOutputChannelMembers()) {
					if (resourceNode.getInSideResources().contains(cm.getResource())) {
						Expression message = cm.getStateTransition().getMessageExpression();
						if (message instanceof Term) {
							// In each resource.
							ArrayList<VariableDeclaration> resInputParams = new ArrayList<>();
							ArrayList<VariableDeclaration> rootInputParams = new ArrayList<>();
							String resourcePath = getInputMethodResourcePathAndPathParams(cm.getResource(), resInputParams, rootInputParams);	// Path parameters for the input REST API.
							if (resourcePath.indexOf('/') > 0) {
								resourcePath = resourcePath.substring(resourcePath.indexOf('/'));
							} else {
								resourcePath = "";
							}
							// The path parameters are not to be passed to the input method of each resource (resInputParams) 
							// because they are always equal to either channel selectors or message parameters.
							
							// Channel parameters to specify the context of the collaboration.
							for (Selector selector: ch.getAllSelectors()) {
								if (selector.getExpression() instanceof Variable) {
									Variable selVar = (Variable) selector.getExpression();
									VariableDeclaration chParam = new VariableDeclaration(selVar.getType(), selVar.getName());
									resInputParams.add(chParam);
								}
							}
							// Message parameters to carry the data-flows.
							for (Map.Entry<Position, Variable> varEnt: message.getVariables().entrySet()) {
								Variable var = varEnt.getValue();
								String refVarName = null;
								for (ChannelMember refCm: ((DataTransferChannel) ch).getReferenceChannelMembers()) {
									Expression varExp = refCm.getStateTransition().getMessageExpression().getSubTerm(varEnt.getKey());
									if (varExp != null && varExp instanceof Variable) {
										if (refCm.getStateTransition().getCurStateExpression().contains(varExp)) {
											refVarName = refCm.getResource().getLeafResourceName();
											break;
										}
									}
								}
								if (refVarName != null) {
									// var has come from a reference resource.
									VariableDeclaration param = new VariableDeclaration(var.getType(), refVarName);
									resInputParams.add(param);
								} else {
									// var has not come from reference resource.
									String paramName = var.getName();
									VariableDeclaration param = new VariableDeclaration(var.getType(), paramName);
									resInputParams.add(param);
									if (!resourcePath.contains("{" + paramName+ "}")) {
										param = new VariableDeclaration(var.getType(), paramName);
										param.addAnnotation(new Annotation("FormParam", "\"" + paramName + "\""));
										rootInputParams.add(param);
									}
								}
							}
							
							if (resourceNode.getResourceHierarchy().getParent() != null && resourceNode.getResourceHierarchy().getParent().getParent() != null) {
								String inputMethodName = ((Term) message).getSymbol().getImplName();
								if (((DataTransferChannel) ch).getOutputChannelMembers().size() > 1) {
									inputMethodName += "For" + getComponentName(cm.getResource().getResourceHierarchy());
								}
								MethodDeclaration input = new MethodDeclaration(inputMethodName, false, typeVoid, resInputParams);
								if (component != null) {
									// A component is created for this resource.
									component.addMethod(input);
								} else {
									// No component is created for this resource.
									Map<String, MethodDeclaration> nameToMethod = inputs.get(resourceNode.getParent().getResourceHierarchy());
									if (nameToMethod == null) {
										nameToMethod = new HashMap<>();
										inputs.put(resourceNode.getParent().getResourceHierarchy(), nameToMethod);
									}
									if (nameToMethod.get(inputMethodName) == null) {
										nameToMethod.put(inputMethodName, input);
									}
								}
							}
							
							// For the root resource.
							if (priorMemberForInputChannel.get(ch) ==null || cm == priorMemberForInputChannel.get(ch)) {
								// If cm is the receiver of the input event.
								priorMemberForInputChannel.put(ch, cm);
								String messageSymbol = ((Term) message).getSymbol().getImplName();
								MethodDeclaration inputAccessor = new MethodDeclaration(messageSymbol, false, typeVoid, rootInputParams);
								if (cm.getStateTransition().isRightUnary()) {
									inputAccessor.addAnnotation(new Annotation("PUT"));
								} else {
									inputAccessor.addAnnotation(new Annotation("POST"));
								}
								if (resourcePath.length() > 0) {
									inputAccessor.addAnnotation(new Annotation("Path", "\"" + resourcePath + "\""));
								}
								inputAccessors.put(resourceNode.getResourceHierarchy(), inputAccessor);
							}
						} else if (message instanceof Variable) {
							// In each resource.
							ArrayList<VariableDeclaration> resInputParams = new ArrayList<>();
							int v = 1;
							if (cm.getResource().getLastParam() != null) {
								Expression pathParam = cm.getResource().getLastParam();
								if (pathParam instanceof Variable) {
									Variable var = (Variable) pathParam;
									String paramName = var.getName();
									VariableDeclaration param = new VariableDeclaration(var.getType(), paramName);
									resInputParams.add(param);
								} else if (pathParam instanceof Term) {
									Term var = (Term) pathParam;
									String paramName = "v" + v;
									VariableDeclaration param = new VariableDeclaration(var.getType(), paramName);
									resInputParams.add(param);
								}
								v++;
							}
							if (cm.getResource().getResourceHierarchy().getParent() != null && cm.getResource().getResourceHierarchy().getParent().getParent() != null) {
								String inputMethodName = ((Variable) message).getName();
								if (((DataTransferChannel) ch).getOutputChannelMembers().size() > 1) {
									inputMethodName += "For" + getComponentName(cm.getResource().getResourceHierarchy());
								}
								MethodDeclaration input = new MethodDeclaration(inputMethodName, false, typeVoid, null);
								if (component != null) {
									// A component is created for this resource.
									component.addMethod(input);
								} else {
									// No component is created for this resource.
									Map<String, MethodDeclaration> nameToMethod = inputs.get(cm.getResource().getParent().getResourceHierarchy());
									if (nameToMethod == null) {
										nameToMethod = new HashMap<>();
										inputs.put(cm.getResource().getParent().getResourceHierarchy(), nameToMethod);
									}
									if (nameToMethod.get(inputMethodName) == null) {
										nameToMethod.put(inputMethodName, input);
									}
								}
							}
							
							// For the root resource.
							if (priorMemberForInputChannel.get(ch) ==null || cm == priorMemberForInputChannel.get(ch)) {
								// If cm is the receiver of the input event.
								priorMemberForInputChannel.put(ch, cm);
								ArrayList<VariableDeclaration> rootInputParams = new ArrayList<>();
								String resourcePath = getGetterResourcePathAndPathParams(cm.getResource(), rootInputParams);
								if (resourcePath.indexOf('/') > 0) {
									resourcePath = resourcePath.substring(resourcePath.indexOf('/'));
								} else {
									resourcePath = "";
								}
								String messageSymbol = ((Variable) message).getName();
								MethodDeclaration inputAccessor = new MethodDeclaration(messageSymbol, false, typeVoid, rootInputParams);
								if (cm.getStateTransition().isRightUnary()) {
									inputAccessor.addAnnotation(new Annotation("PUT"));
								} else {
									inputAccessor.addAnnotation(new Annotation("POST"));
								}
								if (resourcePath.length() > 0) {
									inputAccessor.addAnnotation(new Annotation("Path", "\"" + resourcePath + "\""));
								}
								inputAccessors.put(resourceNode.getResourceHierarchy(), inputAccessor);
							}
						}
					}
				}
			}
		}
		
		// Add leaf getter methods to the parent components.
		for (Map.Entry<ResourceHierarchy, MethodDeclaration> entry: getters) {
			resourceComponents.get(entry.getKey()).addMethod(entry.getValue());
		}
		
		// Add leaf update methods to the parent components.
		for (Map.Entry<ResourceHierarchy, Map<String, MethodDeclaration>> entry: updates.entrySet()) {
			for (MethodDeclaration update: entry.getValue().values()) {
				resourceComponents.get(entry.getKey()).addMethod(update);
			}
		}
		
		// Add leaf input methods to the parent components.
		for (Map.Entry<ResourceHierarchy, Map<String, MethodDeclaration>> entry: inputs.entrySet()) {
			for (MethodDeclaration input: entry.getValue().values()) {
				resourceComponents.get(entry.getKey()).addMethod(input);
			}
		}
		
		// Add leaf reference fields to the parent components.
		for (Map.Entry<ResourceHierarchy, FieldDeclaration> entry: fields) {
			ResourceHierarchy resource = entry.getKey();
			FieldDeclaration field = entry.getValue();
			TypeDeclaration component = resourceComponents.get(resource);
			boolean existsField = false;
			for (FieldDeclaration fld: component.getFields()) {
				if (fld.getName().equals(field.getName())) {
					existsField = true;
					break;
				}
			}
			if (!existsField) {
				component.addField(field);
				if (field.getType().equals(typeClient)) {
					for (CompilationUnit cu: codes) {
						if (cu.types().contains(component)) {
							cu.addImport(new ImportDeclaration("javax.ws.rs.client.*"));
							break;
						}
					}
				}
			}
		}
		
		// Add constructor parameters to the ancestor components.
		for (ResourceNode root: graph.getRootResourceNodes()) {
			addConstructorParameters(root.getResourceHierarchy(), resourceComponents, resourceConstructors, constructorParams);
		}
		
		// Add accessors.
		for (ResourceHierarchy rootRes: model.getResourceHierarchies()) {
			if (rootRes.getParent() == null) {
				// root resource
				TypeDeclaration rootComponent = resourceComponents.get(rootRes);
				// Add getter accessors.
				for (ResourceHierarchy res: getterAccessors.keySet()) {
					if (rootRes.isAncestorOf(res)) {
						rootComponent.addMethod(getterAccessors.get(res));
					}
				}
				// Add input accessors.
				for (ResourceHierarchy res: inputAccessors.keySet()) {
					if (rootRes.isAncestorOf(res)) {
						rootComponent.addMethod(inputAccessors.get(res));
					}
				}
			}
		}
		
		// Declare the Pair class.
		boolean isCreatedPair = false;
		for(Node n : resources) {
			ResourceNode rn = (ResourceNode) n;
			if(isCreatedPair) continue;
			if(model.getType("Pair").isAncestorOf(rn.getResourceStateType())) {
				TypeDeclaration type = new TypeDeclaration("Pair<T>");
				type.addField(new FieldDeclaration(new Type("Double", "T"), "left"));
				type.addField(new FieldDeclaration(new Type("Double", "T"), "right"));
				
				MethodDeclaration constructor = new MethodDeclaration("Pair", true);
				constructor.addParameter(new VariableDeclaration(new Type("Double", "T"), "left"));
				constructor.addParameter(new VariableDeclaration(new Type("Double", "T"), "right"));
				Block block = new Block();
				block.addStatement("this.left = left;");
				block.addStatement("this.right = right;");
				constructor.setBody(block);
				type.addMethod(constructor);
			
				for(FieldDeclaration field : type.getFields()) {
					MethodDeclaration getter = new MethodDeclaration(
						"get" + field.getName().substring(0,1).toUpperCase() + field.getName().substring(1),
						new Type("Double","T"));
					getter.setBody(new Block());
					getter.getBody().addStatement("return " + field.getName() + ";");
					type.addMethod(getter);
				}
				
//				MethodDeclaration toStr = new MethodDeclaration("toString", false, DataConstraintModel.typeString, null);
//				block = new Block();
//				block.addStatement("return \"{\\\"\" + left + \"\\\":\\\"\" + right + \"\\\"}\";");
//				toStr.setBody(block);
//				type.addMethod(toStr);
			
				CompilationUnit	cu = new CompilationUnit(type);
				cu.addImport(new ImportDeclaration("java.util.*"));
				codes.add(cu);
				
				isCreatedPair = true;
			}
		}
				
		return codes;
	}
	
	private static List<VariableDeclaration> addConstructorParameters(ResourceHierarchy resource,
			Map<ResourceHierarchy, TypeDeclaration> resourceComponents,
			Map<ResourceHierarchy, MethodDeclaration> resourceConstructors,
			Map<ResourceHierarchy, Map<String, VariableDeclaration>> constructorParams) {
		List<VariableDeclaration> params = new ArrayList<>();
		for (ResourceHierarchy child: resource.getChildren()) {
			params.addAll(addConstructorParameters(child, resourceComponents, resourceConstructors, constructorParams));
		}
		if (constructorParams.get(resource) != null) {
			for (VariableDeclaration param: constructorParams.get(resource).values()) {
				params.add(param);
			}
		}
		if (params.size() > 0) {
			MethodDeclaration constructor = resourceConstructors.get(resource);
			if (constructor == null) {
				if (resourceComponents.get(resource) != null) {
					String resourceName = getComponentName(resource);
					constructor = new MethodDeclaration(resourceName, true);
					Block body = new Block();
					constructor.setBody(body);
					resourceComponents.get(resource).addMethod(constructor);
					resourceConstructors.put(resource, constructor);
				}
			}
			if (constructor != null) {
				for (VariableDeclaration param: params) {
					constructor.addParameter(param);
					constructor.getBody().addStatement("this." + toVariableName(param.getName()) + " = " + toVariableName(param.getName()) + ";");
				}
			}
		}
		if (resource.getNumParameters() > 0) params.clear();
		return params;
	}

	private static String getGetterResourcePathAndPathParams(ResourcePath resPath, List<VariableDeclaration> pathParams) {
		int v = 1;
		List<String> params = new ArrayList<>();
		for (Expression pathParam: resPath.getPathParams()) {
			if (pathParam instanceof Variable) {
				Variable var = (Variable) pathParam;
				String paramName = var.getName();
				params.add("{" + paramName + "}");
				VariableDeclaration param = new VariableDeclaration(var.getType(), paramName);
				param.addAnnotation(new Annotation("PathParam", "\"" + paramName + "\""));
				pathParams.add(param);
			} else if (pathParam instanceof Term) {
				Term var = (Term) pathParam;
				String paramName = "v" + v;
				params.add("{" + paramName + "}");
				VariableDeclaration param = new VariableDeclaration(var.getType(), paramName);
				param.addAnnotation(new Annotation("PathParam", "\"" + paramName + "\""));
				pathParams.add(param);
			}
			v++;
		}
		return resPath.getResourceHierarchy().toResourcePath(params);
	}
	
	private static String getUpdateResourcePathAndPathParams(ResourcePath resPath, ArrayList<VariableDeclaration> rootParams, boolean isRestAPI) {
		int v = 1;
		List<String> params = new ArrayList<>();
		for (Expression pathParam: resPath.getPathParams()) {
			if (pathParam instanceof Variable) {
				Variable var = (Variable) pathParam;
				String paramName = null;
				if (isRestAPI) {
					paramName = var.getName();					
				} else {
					paramName = "self" + (v > 1 ? v : "");
				}
				params.add("{" + paramName  + "}");
				VariableDeclaration param = new VariableDeclaration(var.getType(), paramName);
				if (isRestAPI) param.addAnnotation(new Annotation("PathParam", "\"" + paramName + "\""));
				rootParams.add(param);
			} else if (pathParam instanceof Term) {
				Term var = (Term) pathParam;
				String paramName = null;
				if (isRestAPI) {
					paramName = "v" + v;					
				} else {
					paramName = "self" + (v > 1 ? v : "");
				}
				params.add("{" + paramName  + "}");
				VariableDeclaration param = new VariableDeclaration(var.getType(), paramName);
				if (isRestAPI) param.addAnnotation(new Annotation("PathParam", "\"" + paramName + "\""));
				rootParams.add(param);
			}
			v++;
		}
		return resPath.getResourceHierarchy().toResourcePath(params);
	}

	private static String getInputMethodResourcePathAndPathParams(ResourcePath resPath, ArrayList<VariableDeclaration> resInputParams, ArrayList<VariableDeclaration> rootInputParams) {
		int v = 1;
		List<String> params = new ArrayList<>();
		if (resPath.getLastParam() != null) {
			Expression pathParam = resPath.getLastParam();
			if (pathParam instanceof Variable) {
				Variable var = (Variable) pathParam;
				String paramName = var.getName();
				params.add("{" + paramName  + "}");
				VariableDeclaration param = new VariableDeclaration(var.getType(), paramName);
				param.addAnnotation(new Annotation("PathParam", "\"" + paramName + "\""));
				rootInputParams.add(param);
			} else if (pathParam instanceof Term) {
				Term var = (Term) pathParam;
				String paramName = "v" + v;
				params.add("{" + paramName  + "}");
				VariableDeclaration param = new VariableDeclaration(var.getType(), paramName);
				param.addAnnotation(new Annotation("PathParam", "\"" + paramName + "\""));
				rootInputParams.add(param);
			}
			v++;
		}
		if (resPath.getParent() != null) {
			for (Expression pathParam: resPath.getParent().getPathParams()) {
				if (pathParam instanceof Variable) {
					Variable var = (Variable) pathParam;
					String paramName = var.getName();
					params.add("{" + paramName  + "}");
					VariableDeclaration param = new VariableDeclaration(var.getType(), paramName);
					param.addAnnotation(new Annotation("PathParam", "\"" + paramName + "\""));
					rootInputParams.add(param);
				} else if (pathParam instanceof Term) {
					Term var = (Term) pathParam;
					String paramName = "v" + v;
					params.add("{" + paramName  + "}");
					VariableDeclaration param = new VariableDeclaration(var.getType(), paramName);
					param.addAnnotation(new Annotation("PathParam", "\"" + paramName + "\""));
					rootInputParams.add(param);
				}
				v++;
			}
		}
		return resPath.getResourceHierarchy().toResourcePath(params);
	}

	private static String getInitializer(ResourceHierarchy res) {
		Type stateType = res.getResourceStateType();
		String initializer = null;
		if (res.getInitialValue() != null) {
			initializer = res.getInitialValue().toImplementation(new String[] {""});
		} else {
			if (DataConstraintModel.typeList.isAncestorOf(stateType)) {
				initializer = "new " + res.getResourceStateType().getImplementationTypeName() + "()";
			} else if (DataConstraintModel.typeMap.isAncestorOf(stateType)) {
				initializer = "new " + res.getResourceStateType().getImplementationTypeName() + "()";
			}
		}
		return initializer;
	}

	static public ArrayList<String> getCodes(ArrayList<TypeDeclaration> codeTree) {
		ArrayList<String> codes = new ArrayList<>();
		for (TypeDeclaration type : codeTree) {
			codes.add("public class " + type.getTypeName() + "{");
			for (FieldDeclaration field : type.getFields()) {
				if (type.getTypeName() != mainTypeName) {
					String cons = "\t" + "private " + field.getType().getInterfaceTypeName() + " "
							+ field.getName();
					if (DataConstraintModel.isListType(field.getType()))
						cons += " = new " + field.getType().getImplementationTypeName() + "()";
					cons += ";";
					codes.add(cons);
				} else {
					String cons = "\t" + "private " + field.getType().getInterfaceTypeName() + " "
							+ field.getName() + " = new " + field.getType().getTypeName() + "(";
					cons += ");";
					codes.add(cons);
				}
			}
			codes.add("");
			for (MethodDeclaration method : type.getMethods()) {
				String varstr = "\t" + "public " + method.getReturnType().getInterfaceTypeName() + " "
						+ method.getName() + "(";
				if (method.getParameters() != null) {
					for (VariableDeclaration var : method.getParameters()) {
						varstr += var.getType().getInterfaceTypeName() + " " + var.getName() + ",";
					}
					if (!method.getParameters().isEmpty())
						varstr = varstr.substring(0, varstr.length() - 1);
				}
				if (method.getBody() != null) {
					for (String str : method.getBody().getStatements()) {
						codes.add("\t\t" + str + ";");
					}
				}
				codes.add(varstr + ")" + "{");
				codes.add("\t" + "}");
				codes.add("");
			}
			codes.add("}");
			codes.add("");
		}
		return codes;
	}

	static public IResourceStateAccessor pushAccessor = new IResourceStateAccessor() {
		@Override
		public Expression getCurrentStateAccessorFor(ChannelMember target, ChannelMember from) {
			ResourcePath targetRes = target.getResource();
			ResourcePath fromRes = from.getResource();
			if (targetRes.equals(fromRes)) {
				return new Field("value",
						targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
								: DataConstraintModel.typeInt);
			}
			// use the cached value as the current state
			return new Field(targetRes.getLeafResourceName(),
					targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
							: DataConstraintModel.typeInt);
		}

		@Override
		public Expression getNextStateAccessorFor(ChannelMember target, ChannelMember from) {
			ResourcePath targetRes = target.getResource();
			return new Parameter(targetRes.getLeafResourceName(),
					targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
							: DataConstraintModel.typeInt);
		}

		@Override
		public Expression getDirectStateAccessorFor(ResourcePath targetRes, ResourcePath fromRes) {
			if (fromRes != null && targetRes.equals(fromRes)) {
				return new Field("value",
						targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
								: DataConstraintModel.typeInt);
			}
			return null;
		}
	};
	static public IResourceStateAccessor pullAccessor = new IResourceStateAccessor() {
		@Override
		public Expression getCurrentStateAccessorFor(ChannelMember target, ChannelMember from) {
			ResourcePath targetRes = target.getResource();
			if (from != null && !target.isOutside()) {
				ResourcePath fromRes = from.getResource();
				if (targetRes.getCommonPrefix(fromRes) != null) {
					return getDirectStateAccessorFor(targetRes, fromRes);
				}
			}
			// for reference channel member
			return new Parameter(targetRes.getLeafResourceName(),
					targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
							: DataConstraintModel.typeInt);
		}

		@Override
		public Expression getNextStateAccessorFor(ChannelMember target, ChannelMember from) {
			ResourcePath targetRes = target.getResource();
			if (from != null && !target.isOutside()) {
				ResourcePath fromRes = from.getResource();
				if (targetRes.getCommonPrefix(fromRes) != null) {
					return getDirectStateAccessorFor(targetRes, fromRes);
				}
			}
			return new Parameter(targetRes.getLeafResourceName(),
					targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
							: DataConstraintModel.typeInt);
		}
		
		@Override
		public Expression getDirectStateAccessorFor(ResourcePath targetRes, ResourcePath fromRes) {
			if (fromRes != null && !fromRes.getResourceHierarchy().isAncestorOf(targetRes.getResourceHierarchy())) {
				if (targetRes.equals(fromRes)) {
					return new Field("value",
							targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
									: DataConstraintModel.typeInt);
				}
				// for reference channel member
				return new Parameter(targetRes.getLeafResourceName(),
						targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
								: DataConstraintModel.typeInt);
			} else {
				// access from an ancestor or outside of the hierarchy
				Stack<ResourcePath> pathStack = new Stack<>();
				ResourcePath curPath = targetRes;
				do {
					if (fromRes != null && curPath.equals(fromRes)) break;
					pathStack.push(curPath);
					curPath = curPath.getParent();					
				} while (curPath != null);
				// iterate from the `from' resource
				Term getter = null;
				int v = 1;
				while (!pathStack.empty()) {
					curPath = pathStack.pop();
					String typeName = getComponentName(curPath.getResourceHierarchy());
					if (getter == null && fromRes == null) {
						// root resource
						String fieldName = toVariableName(typeName);
						getter = new Field(fieldName, new Type(typeName, typeName));
					} else {
						Term newGetter = new Term(new Symbol("get" + typeName, -1, Symbol.Type.METHOD));
						newGetter.addChild(getter);
						if (curPath.getResourceHierarchy().getNumParameters() > 0) {
							Variable var = null;
							Expression param = curPath.getLastParam();
							if (param instanceof Variable) {
								var = (Variable) param;
							} else if (param instanceof Term) {
								var = new Variable("v" + v, ((Term) param).getType());
							}
							if (var != null) {
								newGetter.addChild(var);
								newGetter.getSymbol().setArity(2);
							}
							v++;
						}
						getter = newGetter;
					}
				}
				
				if (generatesComponent(targetRes.getResourceHierarchy())) {
					Term newGetter = new Term(new Symbol("getValue", 1, Symbol.Type.METHOD));
					newGetter.addChild(getter);
					getter = newGetter;
				}
				return getter;
			}
		}
	};
	static public IResourceStateAccessor refAccessor = new IResourceStateAccessor() {
		@Override
		public Expression getCurrentStateAccessorFor(ChannelMember target, ChannelMember from) {
			ResourcePath targetRes = target.getResource();
			ResourcePath fromRes = from.getResource();
			if (targetRes.equals(fromRes)) {
				return new Field("value",
						targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
								: DataConstraintModel.typeInt);
			}
			// for reference channel member
			return new Parameter(targetRes.getLeafResourceName(),
					targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
							: DataConstraintModel.typeInt);
		}

		@Override
		public Expression getNextStateAccessorFor(ChannelMember target, ChannelMember from) {
			ResourcePath targetRes = target.getResource();
			return new Parameter(targetRes.getLeafResourceName(),
					targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
							: DataConstraintModel.typeInt);
		}

		@Override
		public Expression getDirectStateAccessorFor(ResourcePath targetRes, ResourcePath fromRes) {
			if (fromRes != null && targetRes.equals(fromRes)) {
				return new Field("value",
						targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
								: DataConstraintModel.typeInt);
			}
			return null;
		}
	};
}
