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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import code.ast.CompilationUnit;
import code.ast.Block;
import code.ast.MethodDeclaration;
import code.ast.TypeDeclaration;
import code.ast.VariableDeclaration;
import models.algebra.Expression;
import models.algebra.Field;
import models.algebra.Parameter;
import models.algebra.Symbol;
import models.algebra.Term;
import models.algebra.Type;
import models.algebra.Variable;
import models.dataConstraintModel.ChannelMember;
import models.dataConstraintModel.DataConstraintModel;
import models.dataConstraintModel.ListType;
import models.dataConstraintModel.MapType;
import models.dataConstraintModel.ResourceHierarchy;
import models.dataConstraintModel.ResourcePath;
import models.dataConstraintModel.Selector;
import models.dataFlowModel.DataTransferChannel;
import models.dataFlowModel.DataTransferModel;
import models.dataFlowModel.IFlowGraph;
import models.dataFlowModel.ResourceNode;
import models.dataFlowModel.DataTransferChannel.IResourceStateAccessor;

public class CodeGenerationContext {
	protected DataTransferModel model = null;
	protected IFlowGraph flowGraph = null;
	protected ArrayList<CompilationUnit> AST;
	protected HashMap<String, HashMap<ResourceHierarchy, String>> componentNames = new HashMap<>();
	protected Map<ResourceHierarchy, TypeDeclaration> resourceHierarchyToComponent;
	protected Map<String, Type> componentNameToType;
	protected Map<ResourceHierarchy, Map<ResourceHierarchy, Map<DataTransferChannel, String>>> updateMethods;
	protected ILanguageSpecific langSpec = null;
	protected IPlatformSpecific platformSpec = null;
	
	public CodeGenerationContext(ILanguageSpecific langSpec, IPlatformSpecific platformSpec) {
		this.resourceHierarchyToComponent = new HashMap<>();
		this.componentNameToType = new HashMap<>();
		this.updateMethods = new HashMap<>();
		this.langSpec = langSpec;
		this.platformSpec = platformSpec;
	}
	
	public void init(DataTransferModel model, IFlowGraph flowGraph) {
		this.setModel(model);
		this.setFlowGraph(flowGraph);
		this.AST = new ArrayList<>();
	}
	
	public void setModel(DataTransferModel model) {
		this.model = model;
	}
	
	public DataTransferModel getModel() {
		return this.model;
	}
	
	public void setFlowGraph(IFlowGraph flowGraph) {
		this.flowGraph = flowGraph;
	}
	
	public IFlowGraph getFlowGraph() {
		return this.flowGraph;
	}

	public CompilationUnit createCompilationUnit(ResourceHierarchy resourceHierarchy, TypeDeclaration component) {
		CompilationUnit cu = langSpec.newCompilationUnit(component);
		if (!platformSpec.isMonolithic()  && resourceHierarchy.getParent() == null) {
			// For each root node, add platform specific imports.
			((RestApiSpecific) platformSpec).addPlatformSpecificImports(cu);
		}
		addCompilationUnit(cu);
		resourceHierarchyToComponent.put(resourceHierarchy, component);
		return cu;
	}
	
	public void addCompilationUnit(CompilationUnit compilationUnit) {
		this.AST.add(compilationUnit);
	}
	
	public void putComponent(ResourceHierarchy res, TypeDeclaration component) {
		resourceHierarchyToComponent.put(res, component);
	}
	
	public TypeDeclaration getComponent(ResourceHierarchy res) {
		return resourceHierarchyToComponent.get(res);
	}

	public ArrayList<CompilationUnit> getAST() {
		return this.AST;
	}

	public String getComponentName(ResourceHierarchy res, ILanguageSpecific langSpec) {
		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";
			}
		}
		String componentName = langSpec.toComponentName(name);
		if (!CodeGenerator.generatesComponent(res)) return componentName;
		// To avoid generating multiple components with the same name.
		HashMap<ResourceHierarchy, String> resToName = componentNames.get(componentName);
		if (resToName == null) {
			resToName = new HashMap<>();
			resToName.put(res, componentName);
			componentNames.put(componentName, resToName);
			return componentName;
		}
		if (resToName.get(res) == null) {
			componentName += resToName.size();
			resToName.put(res, componentName);
			return componentName;
		}
		return resToName.get(res);
	}
	
	public Type getOrCreateComponentType(ResourceHierarchy res) {
		return getOrCreateComponentType(getComponentName(res, langSpec));
	}
	
	public Type getOrCreateComponentType(String componentName) {
		Type componentType = componentNameToType.get(componentName);
		if (componentType != null) return componentType;
		componentType = new Type(componentName, new code.ast.SimpleType(componentName));
		componentNameToType.put(componentName, componentType);
		return componentType;
	}
	
	public Type getImplStateType(ResourceHierarchy res, ILanguageSpecific langSpec) {
		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()) || res.getResourceStateType() instanceof ListType) {
					// list.
					if (CodeGenerator.generatesComponent(child)) {
						return langSpec.newListType(getOrCreateComponentType(child));
					} else {
						return langSpec.newListType(getImplStateType(child, langSpec));
					}
				} else if (DataConstraintModel.typeMap.isAncestorOf(res.getResourceStateType()) || res.getResourceStateType() instanceof MapType) {
					// map.
					if (CodeGenerator.generatesComponent(child)) {
						return langSpec.newMapType(DataConstraintModel.typeString, getOrCreateComponentType(child).getInterfaceTypeName());
					} else {
						return langSpec.newMapType(DataConstraintModel.typeString, getImplStateType(child, langSpec).getInterfaceTypeName());
					}
				}
				return null;
			} else {
				// class
				return res.getResourceStateType();
			}
		}
	}
	
	public MethodDeclaration getConstructor(TypeDeclaration component) {
		for (MethodDeclaration m: component.getMethods()) {
			if (m.isConstructor()) return m;
		}
		return null;
	}
	
	public MethodDeclaration getMethod(TypeDeclaration component, String methodName, List<VariableDeclaration> params) {
		for (MethodDeclaration m: component.getMethods()) {
			if (m.getName().equals(methodName)) {
				if (m.getParameters() == null && (params == null || params.size() == 0)) return m;
				if (m.getParameters() != null && params != null && m.getParameters().size() == params.size()) {
					boolean matchParams = true;
					for (int i = 0; i < m.getParameters().size(); i++) {
						if (!m.getParameters().get(i).getType().equals(params.get(i).getType())) {
							matchParams = false;
							break;
						}
					}
					if (matchParams) return m;
				}
			}
		}
		return null;
	}

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

	public String getOrPutUpdateMethodName(ResourceHierarchy srcRes, DataTransferChannel ch, ResourceHierarchy dstRes) {
		Map<ResourceHierarchy, Map<DataTransferChannel, String>> dstResUpdatesMethods = updateMethods.getOrDefault(dstRes, new HashMap<>());
		updateMethods.put(dstRes, dstResUpdatesMethods);
		Map<DataTransferChannel, String> dstResFromSrcResUpdatesMethods = dstResUpdatesMethods.getOrDefault(srcRes, new HashMap<>());
		dstResUpdatesMethods.put(srcRes, dstResFromSrcResUpdatesMethods);
		String updateMethodName = dstResFromSrcResUpdatesMethods.get(ch);
		if (updateMethodName == null) {
			String srcResComponentName = getComponentName(srcRes, langSpec);
			String dstResComponentName = getComponentName(dstRes, langSpec);
			if (CodeGenerator.generatesComponent(dstRes)) {
				updateMethodName = CodeGenerator.updateMethodPrefix + CodeGenerator.from + srcResComponentName;
			} else if (dstRes.getParent() != null) {
				updateMethodName = CodeGenerator.updateMethodPrefix + dstResComponentName + CodeGenerator.from + srcResComponentName;
			}
			if (dstResFromSrcResUpdatesMethods.size() > 0) {
				updateMethodName += dstResFromSrcResUpdatesMethods.size() + 1;		// To avoid declaring the method multiply.
			}
			dstResFromSrcResUpdatesMethods.put(ch, updateMethodName);
		}
		return updateMethodName;
	}
	
	public MethodDeclaration getOrDeclareUpdateMethod(ResourceHierarchy srcRes, ResourceHierarchy dstRes, DataTransferChannel ch, ArrayList<VariableDeclaration> parameters) {
		MethodDeclaration update = null;
		String updateMethodName = getOrPutUpdateMethodName(srcRes, ch, dstRes);
		if (CodeGenerator.generatesComponent(dstRes)) {
			// A component is created for this resource.
			TypeDeclaration dstComponent = getComponent(dstRes);				
			for (MethodDeclaration method: dstComponent.getMethods()) {
				if (method.getName().equals(updateMethodName)) {
					update = method;
					break;
				}
			}
			if (update == null) {
				update = langSpec.newMethodDeclaration(updateMethodName, false, null, parameters);
				dstComponent.addMethod(update);
			}
		} else if (dstRes.getParent() != null) {
			// No component is created for this resource.
			TypeDeclaration dstParentComponent = getComponent(dstRes.getParent());
			for (MethodDeclaration method: dstParentComponent.getMethods()) {
				if (method.getName().equals(updateMethodName)) {
					update = method;
					break;
				}
			}
			if (update == null) {
				update = langSpec.newMethodDeclaration(updateMethodName, false, null, parameters);
				dstParentComponent.addMethod(update);
			}
		}
		return update;
	}

	public void declareGetterAccessorInTheRootResource(ResourceNode resourceNode, TypeDeclaration rootComponent) {
		if (resourceNode.getResourceHierarchy().getParent() != null) {
			// For a non-root resource
			MethodDeclaration getterAccessor = null;
			List<VariableDeclaration> mainGetterParams = new ArrayList<>();
			String resourcePath = getGetterResourcePathAndPathParams(resourceNode.getPrimaryResourcePath(), mainGetterParams, platformSpec, langSpec);
			if (resourcePath.indexOf('/') > 0) {
				// Remove the root resource from the path
				resourcePath = resourcePath.substring(resourcePath.indexOf('/'));
			} else {
				resourcePath = "";
			}
			if (mainGetterParams.size() > 0) {
				getterAccessor = langSpec.newMethodDeclaration(CodeGeneratorFromDataFlowGraph.getterPrefix + getComponentName(resourceNode.getResourceHierarchy(), langSpec) + CodeGeneratorFromDataFlowGraph.methoNameOfResourceState,
													false,
													getImplStateType(resourceNode.getResourceHierarchy(), langSpec),
													mainGetterParams);
			} else {
				getterAccessor = langSpec.newMethodDeclaration(CodeGeneratorFromDataFlowGraph.getterPrefix + getComponentName(resourceNode.getResourceHierarchy(), langSpec) + CodeGeneratorFromDataFlowGraph.methoNameOfResourceState,
													getImplStateType(resourceNode.getResourceHierarchy(), langSpec));
			}
			getterAccessor.setBody(new Block());
			ResourcePath resPath = new ResourcePath(resourceNode.getPrimaryResourcePath());
			for (int i = 0; i < mainGetterParams.size(); i++) {
				Parameter pathParam = new Parameter(mainGetterParams.get(i).getName());
				resPath.replacePathParam(i, pathParam, null);
			}
			Expression getState = getPullAccessor(platformSpec).getDirectStateAccessorFor(resPath, resPath.getRoot());
			getterAccessor.getBody().addStatement(langSpec.getReturnStatement(getState.toImplementation(new String[] {null})));
			if (!platformSpec.isMonolithic()) {
				((RestApiSpecific) platformSpec).addGetAnnotations(getterAccessor);
				if (resourcePath.length() > 0) {
					((RestApiSpecific) platformSpec).addPathAnnotation(getterAccessor, resourcePath);
				}
			}
			rootComponent.addMethod(getterAccessor);
		}
	}

	public String getGetterResourcePathAndPathParams(ResourcePath resPath, List<VariableDeclaration> pathParams, IPlatformSpecific platformSpec, ILanguageSpecific langSpec) {
		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 = langSpec.newVariableDeclaration(var.getType(), paramName);
				if (!platformSpec.isMonolithic()) ((RestApiSpecific) platformSpec).addPathParamAnnotation(param, paramName);
				pathParams.add(param);
			} else if (pathParam instanceof Term) {
				Term var = (Term) pathParam;
				String paramName = "v" + v;
				params.add("{" + paramName + "}");
				VariableDeclaration param = langSpec.newVariableDeclaration(var.getType(), paramName);
				if (!platformSpec.isMonolithic()) ((RestApiSpecific) platformSpec).addPathParamAnnotation(param, paramName);
				pathParams.add(param);
			}
		}
		return resPath.getResourceHierarchy().toResourcePath(params);
	}

	public void declareUpdateAccessorInTheRootResource(ResourceNode resourceNode, String updateMethodName, DataTransferChannel ch, ChannelMember cm, ResourcePath srcResPath, ResourcePath dstResPath, TypeDeclaration rootComponent, int inDegree, IPlatformSpecific platformSpec, ILanguageSpecific langSpec) {
		ArrayList<VariableDeclaration> parameters;
		VariableDeclaration param;
		parameters = new ArrayList<>();
		String resourcePath = getUpdateResourcePathAndPathParams(dstResPath, parameters, true, platformSpec, langSpec);	// Path parameters to identify the self resource.
		if (resourcePath.indexOf('/') > 0) {
			// Remove the root resource from the path
			resourcePath = resourcePath.substring(resourcePath.indexOf('/'));
		} else {
			resourcePath = "";
		}
		ResourcePath resPath = new ResourcePath(dstResPath);
		for (int i = 0; i < parameters.size(); i++) {
			Parameter pathParam = new Parameter(parameters.get(i).getName());
			resPath.replacePathParam(i, pathParam, null);
		}
		for (Selector selector: ch.getAllSelectors()) {
			if (selector.getExpression() instanceof Variable) {
				Variable selVar = (Variable) selector.getExpression();
				VariableDeclaration chParam = langSpec.newVariableDeclaration(selVar.getType(), selVar.getName());
				if (!platformSpec.isMonolithic()) {
					((RestApiSpecific) platformSpec).addFormParamAnnotation(chParam, selVar.getName());
				}
				parameters.add(chParam);	// A channel parameter to specify the context of the collaboration.
			}
		}
		Type srcType = srcResPath.getResourceStateType();
		String srcResName = langSpec.toVariableName(getComponentName(srcResPath.getResourceHierarchy(), langSpec));
		param = langSpec.newVariableDeclaration(srcType, srcResName);
		if (!platformSpec.isMonolithic()) ((RestApiSpecific) platformSpec).addFormParamAnnotation(param, srcResName);
		parameters.add(param);	// The state of the source resource to carry the data-flow.
		for (ResourcePath refRes: ch.getReferenceResources()) {
			if (!refRes.equals(resourceNode.getInSideResource(ch))) {
				String refName = langSpec.toVariableName(getComponentName(refRes.getResourceHierarchy(), langSpec));
				param = langSpec.newVariableDeclaration(refRes.getResourceStateType(), refName);
				if (!platformSpec.isMonolithic()) {
					((RestApiSpecific) platformSpec).addFormParamAnnotation(param, refName);
				}
				parameters.add(param);
			}
		}
		MethodDeclaration updateAccessor = langSpec.newMethodDeclaration(updateMethodName, false, null, parameters);
		if (!platformSpec.isMonolithic()) {
			if (isPut(cm)) {
				((RestApiSpecific) platformSpec).addPutAnnotations(updateAccessor);
			} else {
				if (!isDelete(cm)) {
					((RestApiSpecific) platformSpec).addPostAnnotations(updateAccessor);
				} else {
					((RestApiSpecific) platformSpec).addDeleteAnnotations(updateAccessor);
				}
			}
		}
		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 += "/" + langSpec.toVariableName(srcResName);
		}
		if (!platformSpec.isMonolithic()) ((RestApiSpecific) platformSpec).addPathAnnotation(updateAccessor, resourcePath);
		
		// To make the accessor call the update method.
		Expression resExp = getPullAccessor(platformSpec).getDirectStateAccessorFor(resPath, resPath.getRoot());
		List<String> args = new ArrayList<>();
		if (resExp instanceof Term) {
			// To access the parent resource if the leaf resource is primitive and cannot declare the update method, or to remove getValue().
			if (((Term) resExp).getChildren().size() > 1 && ((Term) resExp).getChild(1) instanceof Variable) {
				args.add(((Variable)((Term) resExp).getChild(1)).getName());
			}
			resExp = ((Term) resExp).getChild(0);
		}
		for (VariableDeclaration var: updateAccessor.getParameters()) {
			args.add(var.getName());
		}
		if (resExp != null) {
			String resourceAccess = resExp.toImplementation(new String[] {""});
			updateAccessor.addStatement(langSpec.getMethodInvocation(resourceAccess, updateMethodName, args) + langSpec.getStatementDelimiter());
		} else {
			updateAccessor.addStatement(langSpec.getMethodInvocation(updateMethodName, args) + langSpec.getStatementDelimiter());
		}
		rootComponent.addMethod(updateAccessor);
	}

	public String getUpdateResourcePathAndPathParams(ResourcePath resPath, ArrayList<VariableDeclaration> pathParams, boolean isRestAPI, IPlatformSpecific platformSpec, ILanguageSpecific langSpec) {
		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 = langSpec.newVariableDeclaration(var.getType(), paramName);
				if (isRestAPI) ((RestApiSpecific) platformSpec).addPathParamAnnotation(param, paramName);
				pathParams.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 = langSpec.newVariableDeclaration(var.getType(), paramName);
				if (isRestAPI) ((RestApiSpecific) platformSpec).addPathParamAnnotation(param, paramName);
				pathParams.add(param);
			}
			v++;
		}
		return resPath.getResourceHierarchy().toResourcePath(params);
	}

	public MethodDeclaration declareInputAccessorInTheRootResource(String inputMethodName,
			ArrayList<VariableDeclaration> rootInputParams, ChannelMember cm, String resourcePath,
			TypeDeclaration rootComponent, IPlatformSpecific platformSpec, ILanguageSpecific langSpec) {
		MethodDeclaration rootInputAccessor;
		rootInputAccessor = langSpec.newMethodDeclaration(inputMethodName, false, null, rootInputParams);
		if (!platformSpec.isMonolithic()) {
			if (isPut(cm)) {
				((RestApiSpecific) platformSpec).addPutAnnotations(rootInputAccessor);
			} else {
				if (!isDelete(cm)) {
					((RestApiSpecific) platformSpec).addPostAnnotations(rootInputAccessor);
				} else {
					((RestApiSpecific) platformSpec).addDeleteAnnotations(rootInputAccessor);
				}
			}
			if (resourcePath.length() > 0) {
				((RestApiSpecific) platformSpec).addPathAnnotation(rootInputAccessor, resourcePath);
			}
		}
		rootComponent.addMethod(rootInputAccessor);
		return rootInputAccessor;
	}

	public String getInputMethodResourcePathAndPathParams(ResourcePath resPath, ArrayList<VariableDeclaration> rootInputParams, IPlatformSpecific platformSpec, ILanguageSpecific langSpec) {
		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 = langSpec.newVariableDeclaration(var.getType(), paramName);
				if (!platformSpec.isMonolithic()) ((RestApiSpecific) platformSpec).addPathParamAnnotation(param, paramName);
				rootInputParams.add(param);
			} else if (pathParam instanceof Term) {
				Term var = (Term) pathParam;
				String paramName = "v" + v;
				params.add("{" + paramName  + "}");
				VariableDeclaration param = langSpec.newVariableDeclaration(var.getType(), paramName);
				if (!platformSpec.isMonolithic()) ((RestApiSpecific) platformSpec).addPathParamAnnotation(param, 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 = langSpec.newVariableDeclaration(var.getType(), paramName);
					if (!platformSpec.isMonolithic()) ((RestApiSpecific) platformSpec).addPathParamAnnotation(param, paramName);
					rootInputParams.add(param);
				} else if (pathParam instanceof Term) {
					Term var = (Term) pathParam;
					String paramName = "v" + v;
					params.add("{" + paramName  + "}");
					VariableDeclaration param = langSpec.newVariableDeclaration(var.getType(), paramName);
					if (!platformSpec.isMonolithic()) ((RestApiSpecific) platformSpec).addPathParamAnnotation(param, paramName);
					rootInputParams.add(param);
				}
				v++;
			}
		}
		return resPath.getResourceHierarchy().toResourcePath(params);
	}
	
	public boolean isPut(ChannelMember cm) {
		return cm.getStateTransition().isRightUnary();
	}

	public boolean isDelete(ChannelMember cm) {
		boolean isDelete = false;
		Expression nextExp = cm.getStateTransition().getNextStateExpression();
		if (nextExp instanceof Term) {
			Symbol rootSymbol = ((Term) nextExp).getSymbol();
			if (rootSymbol.equals(DataConstraintModel.delete) || rootSymbol.equals(DataConstraintModel.remove)) {
				isDelete = true;
			} else if (rootSymbol.equals(DataConstraintModel.cond)) {
				Expression childExp = ((Term) nextExp).getChild(1);
				if (childExp instanceof Term) {
					rootSymbol = ((Term) childExp).getSymbol();
					if (rootSymbol.equals(DataConstraintModel.delete) || rootSymbol.equals(DataConstraintModel.remove)) {
						isDelete = true;
					}
				}
				childExp = ((Term) nextExp).getChild(2);
				if (childExp instanceof Term) {
					rootSymbol = ((Term) childExp).getSymbol();
					if (rootSymbol.equals(DataConstraintModel.delete) || rootSymbol.equals(DataConstraintModel.remove)) {
						isDelete = true;
					}
				}
			}
		}
		return isDelete;
	}
	
	public IResourceStateAccessor getPushAccessor(IPlatformSpecific platformSpec) {
		if (platformSpec.isMonolithic()) {
			return 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(CodeGenerator.fieldOfResourceState,
								targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
										: DataConstraintModel.typeInt);
					}
					// use the cached value as the current state
					return new Field(langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy(), langSpec)),
							targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
									: DataConstraintModel.typeInt);
				}
	
				@Override
				public Expression getNextStateAccessorFor(ChannelMember target, ChannelMember from) {
					ResourcePath targetRes= target.getResource();
					return new Parameter(langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy(), langSpec)),
							targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
									: DataConstraintModel.typeInt);
				}
	
				@Override
				public Expression getDirectStateAccessorFor(ResourcePath targetRes, ResourcePath fromRes) {
					if (fromRes != null && targetRes.equals(fromRes)) {
						return new Field(CodeGenerator.fieldOfResourceState,
								targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
										: DataConstraintModel.typeInt);
					}
					// for reference channel member
					return new Parameter(langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy(), langSpec)),
							targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
									: DataConstraintModel.typeInt);
				}
			};
		} else {
			return 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(CodeGenerator.fieldOfResourceState,
								targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
										: DataConstraintModel.typeInt);
					}
					// use the cached value as the current state
					return new Field(langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy(), langSpec)),
							targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
									: DataConstraintModel.typeInt);
				}

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

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

	public IResourceStateAccessor getPullAccessor(IPlatformSpecific platformSpec) {
		if (platformSpec.isMonolithic()) {
			return new IResourceStateAccessor() {
				@Override
				public Expression getCurrentStateAccessorFor(ChannelMember target, ChannelMember from) {
					ResourcePath targetRes= target.getResource();
					if (from != null) {
						ResourcePath fromRes= from.getResource();
						if (!target.isOutside()) {
							return getDirectStateAccessorFor(targetRes, fromRes);
						}
						Term getter = null;
						String targetComponentName = getComponentName(targetRes.getResourceHierarchy(), langSpec);
						if (CodeGenerator.generatesComponent(targetRes.getResourceHierarchy())) {
							getter = new Term(new Symbol(CodeGenerator.getterOfResourceState, 1, Symbol.Type.METHOD));
							getter.addChild(new Field(langSpec.toVariableName(targetComponentName), targetRes.getResourceStateType()));
						} else {
							String parentName = langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy().getParent(), langSpec));
							Type parentType = targetRes.getResourceHierarchy().getParent().getResourceStateType();
							getter = new Term(new Symbol(CodeGenerator.getterPrefix + targetComponentName, 1, Symbol.Type.METHOD));
							getter.addChild(new Field(parentName, parentType));
						}
						return getter;
					} else {
						return getDirectStateAccessorFor(targetRes, null);
					}
				}
	
				@Override
				public Expression getNextStateAccessorFor(ChannelMember target, ChannelMember from) {
					ResourcePath targetRes= target.getResource();
					if (from != null) {
						ResourcePath fromRes= from.getResource();
						if (!target.isOutside()) {
							return getDirectStateAccessorFor(targetRes, fromRes);
						}
						Term getter = null;
						String targetComponentName = getComponentName(targetRes.getResourceHierarchy(), langSpec);
						if (CodeGenerator.generatesComponent(targetRes.getResourceHierarchy())) {
							getter = new Term(new Symbol(CodeGenerator.getterOfResourceState, 1, Symbol.Type.METHOD));
							getter.addChild(new Field(langSpec.toVariableName(targetComponentName), targetRes.getResourceStateType()));
						} else {
							String parentName = langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy().getParent(), langSpec));
							Type parentType = targetRes.getResourceHierarchy().getParent().getResourceStateType();
							getter = new Term(new Symbol(CodeGenerator.getterPrefix + targetComponentName, 1, Symbol.Type.METHOD));
							getter.addChild(new Field(parentName, parentType));
						}
						return getter;
					} else {
						return getDirectStateAccessorFor(targetRes, null);
					}
				}
				
				@Override
				public Expression getDirectStateAccessorFor(ResourcePath targetRes, ResourcePath fromRes) {
					if (fromRes != null) {
						if (targetRes.equals(fromRes)) {
							return new Field(CodeGenerator.fieldOfResourceState,
									targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
											: DataConstraintModel.typeInt);
						}
						// for reference channel member
						if (fromRes.isAncestorOf(targetRes)) {
							Stack<ResourcePath> pathStack = new Stack<>();
							ResourcePath curPath = targetRes;
							do {
								pathStack.push(curPath);
								curPath = curPath.getParent();					
							} while (!curPath.equals(fromRes));
							// iterate from the fromRes resource
							return getRelativePath(targetRes, pathStack);
						}
						if (CodeGenerator.generatesComponent(targetRes.getResourceHierarchy())) {
							Term getter = new Term(new Symbol(CodeGenerator.getterOfResourceState, 1, Symbol.Type.METHOD));
							getter.addChild(new Field(langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy(), langSpec)), targetRes.getResourceStateType()));
							return getter;
						} else {
							return new Field(langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy(), langSpec)), targetRes.getResourceStateType());
						}
					} else {
						// (#3) access from the outside of the hierarchy (must be kept consistent with #4)
						Stack<ResourcePath> pathStack = new Stack<>();
						ResourcePath curPath = targetRes;
						do {
							pathStack.push(curPath);
							curPath = curPath.getParent();					
						} while (curPath != null);
						// iterate from the root resource
						return getRelativePath(targetRes, pathStack);
					}
				}
	
				private Expression getRelativePath(ResourcePath targetRes, Stack<ResourcePath> pathStack) {
					ResourcePath curPath;
					Term getter = null;
					int arity = 2;
					boolean doesChainInvocations = true;
					while (!pathStack.empty()) {
						curPath = pathStack.pop();
						String typeName = getComponentName(curPath.getResourceHierarchy(), langSpec);
						if (getter == null && CodeGenerator.generatesComponent(curPath.getResourceHierarchy())) {
							// root resource
							String fieldName = langSpec.toVariableName(typeName);
							getter = new Field(fieldName, getOrCreateComponentType(typeName));
						} else {
							if (CodeGenerator.generatesComponent(curPath.getResourceHierarchy())) {
								if (arity == 2) {
									Term newGetter = new Term(new Symbol(CodeGenerator.getterPrefix + typeName, -1, Symbol.Type.METHOD));
									newGetter.addChild(getter);
									if (curPath.getResourceHierarchy().getNumParameters() > 0) {
										Expression param = curPath.getLastParam();
										if (param != null) {
											newGetter.addChild(param);
											newGetter.getSymbol().setArity(2);
										}
									}
									getter = newGetter;
								} else {
									// add the last path parameter.
									if (curPath.getResourceHierarchy().getNumParameters() > 0) {
										Expression param = curPath.getLastParam();
										if (param != null) {
											getter.getSymbol().setArity(arity);
											getter.addChild(param);
										}
									}
								}
								arity = 2;
								doesChainInvocations = true;
							} else {
								// to get a descendant resource directly. (e.g, .todos.{year}.{month}.{day}.{id} ==> .getTodos().getTodo(year, month, day, id))
								if (doesChainInvocations) {
									Term newGetter = new Term(new Symbol(CodeGenerator.getterPrefix + typeName, -1, Symbol.Type.METHOD));
									newGetter.addChild(getter);
									getter = newGetter;
									doesChainInvocations = false;
								}
								if (curPath.getResourceHierarchy().getNumParameters() > 0) {
									// may change the symbol name
									getter.getSymbol().changeName(CodeGenerator.getterPrefix + typeName);
									// add a path parameter.
									Expression param = curPath.getLastParam();
									if (param != null) {
										getter.getSymbol().setArity(arity);
										getter.addChild(param);
										arity++;
									}
								}
							}
						}
					}
					
					if (CodeGenerator.generatesComponent(targetRes.getResourceHierarchy())) {
						Term newGetter = new Term(new Symbol(CodeGenerator.getterOfResourceState, 1, Symbol.Type.METHOD));
						newGetter.addChild(getter);
						getter = newGetter;
					}
					return getter;
				}
			};
		} else {
			return 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(langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy(), langSpec)),
							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(langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy(), langSpec)),
							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(CodeGenerator.fieldOfResourceState,
									targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
											: DataConstraintModel.typeInt);
						}
						// for reference channel member
						return new Parameter(langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy(), langSpec)),
								targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
										: DataConstraintModel.typeInt);
					} else {
						// (#3) access from an ancestor or outside of the hierarchy (must be kept consistent with #4)
						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 arity = 2;
						boolean doesChainInvocations = true;
						while (!pathStack.empty()) {
							curPath = pathStack.pop();
							String typeName = getComponentName(curPath.getResourceHierarchy(), langSpec);
							if (getter == null && fromRes == null) {
								// root resource
								String fieldName = langSpec.toVariableName(typeName);
								getter = new Field(fieldName, getOrCreateComponentType(typeName));
							} else {
								if (CodeGenerator.generatesComponent(curPath.getResourceHierarchy())) {
									if (arity == 2) {
										Term newGetter = new Term(new Symbol(CodeGenerator.getterPrefix + typeName, -1, Symbol.Type.METHOD));
										newGetter.addChild(getter);
										if (curPath.getResourceHierarchy().getNumParameters() > 0) {
											Expression param = curPath.getLastParam();
											if (param != null) {
												newGetter.addChild(param);
												newGetter.getSymbol().setArity(2);
											}
										}
										getter = newGetter;
									} else {
										// add the last path parameter.
										if (curPath.getResourceHierarchy().getNumParameters() > 0) {
											Expression param = curPath.getLastParam();
											if (param != null) {
												getter.getSymbol().setArity(arity);
												getter.addChild(param);
											}
										}
									}
									arity = 2;
									doesChainInvocations = true;
								} else {
									// to get a descendant resource directly. (e.g, .todos.{year}.{month}.{day}.{id} ==> .getTodos().getTodo(year, month, day, id))
									if (doesChainInvocations) {
										Term newGetter = new Term(new Symbol(CodeGenerator.getterPrefix + typeName, -1, Symbol.Type.METHOD));
										newGetter.addChild(getter);
										getter = newGetter;
										doesChainInvocations = false;
									}
									if (curPath.getResourceHierarchy().getNumParameters() > 0) {
										// may change the symbol name
										getter.getSymbol().changeName(CodeGenerator.getterPrefix + typeName);
										// add a path parameter.
										Expression param = curPath.getLastParam();
										if (param != null) {
											getter.getSymbol().setArity(arity);
											getter.addChild(param);
											arity++;
										}
									}
								}
							}
						}
						
						if (CodeGenerator.generatesComponent(targetRes.getResourceHierarchy())) {
							Term newGetter = new Term(new Symbol(CodeGenerator.getterOfResourceState, 1, Symbol.Type.METHOD));
							newGetter.addChild(getter);
							getter = newGetter;
						}
						return getter;
					}
				}
			};
		}
	}
	
	public IResourceStateAccessor getRefAccessor(IPlatformSpecific platformSpec) {
		if (platformSpec.isMonolithic()) {
			return 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(CodeGenerator.fieldOfResourceState,
								targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
										: DataConstraintModel.typeInt);
					}
					// use the cached value as the current state
					return new Parameter(langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy(), langSpec)),
							targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
									: DataConstraintModel.typeInt);
				}
	
				@Override
				public Expression getNextStateAccessorFor(ChannelMember target, ChannelMember from) {
					ResourcePath targetRes= target.getResource();
					return new Parameter(langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy(), langSpec)),
							targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
									: DataConstraintModel.typeInt);
				}
	
				@Override
				public Expression getDirectStateAccessorFor(ResourcePath targetRes, ResourcePath fromRes) {
					if (fromRes != null && targetRes.equals(fromRes)) {
						return new Field(CodeGenerator.fieldOfResourceState,
								targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
										: DataConstraintModel.typeInt);
					}
					// for reference channel member
					return new Parameter(langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy(), langSpec)),
							targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
									: DataConstraintModel.typeInt);
				}
			};
		} else {
			return 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(CodeGenerator.fieldOfResourceState,
								targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
										: DataConstraintModel.typeInt);
					}
					// for reference channel member
					return new Parameter(langSpec.toVariableName(getComponentName(targetRes.getResourceHierarchy(), langSpec)),
							targetRes.getResourceStateType() != null ? targetRes.getResourceStateType()
									: DataConstraintModel.typeInt);
				}

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

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