Newer
Older
DesignCraft / src / main / java / generators / ASTGenerator.java
package generators;

import java.util.ArrayList;
import java.util.List;

import ast.*;
import designPatternExtensions.DependencyInversion;
import designPatternExtensions.InterfaceNode;
import designPatternExtensions.MediatorInsertion;
import models.dataConstraintModel.MapType;
import models.deltaAlgebra.*;
import models.dataSynchronizationModel.*;

public class ASTGenerator {
	public static final String getterPrefix = "get";
	public static final String setterPrefix = "set";
	public static final String addMethodPrefix = "add";
	public static final String updateMethodPrefix = "update";
	public static final String idPostfix = "Id";
	public static final String mapPostfix = "Map";
	public static final String mapGet = "get";
	public static final String mapPut = "put";
	public static final String mapValues = "values";
	public static final String mapKeySet = "keySet";

	public static String toComponentName(String name) {
		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 Codebase generate(DataSynchronizationDesign dataSynchronizationDesign) {
		Codebase codebase = new Codebase();
		DeltaComplex deltaComplex = dataSynchronizationDesign.getDeltaComplex();
		for (Delta delta: deltaComplex.decompose()) {
			codebase = weaveDelta(codebase, delta);
		}
		// do not change flow : MediatorInsertion -> DependencyInversion
		for (MediatorInsertion mi: dataSynchronizationDesign.getMediatorInsertions()) {
			ObjectNode srcNode, dstNode;
			if (mi.getPushPullValue() == TransferStyle.PUSH) {
				srcNode = mi.getSrc();
				dstNode = mi.getDst();
			} else {
				srcNode = mi.getDst();
				dstNode = mi.getSrc();
			}
			codebase = MediatorInsertionWeaver.weave(codebase, mi, srcNode, dstNode);

		}
		for (DependencyInversion di: dataSynchronizationDesign.getDependencyInversions()) {
			codebase = DependencyInversionWeaver.weave(codebase, di);
		}
		return codebase;
	}

	public static Codebase weaveDelta(Codebase codebase, Delta delta) {
		List<PrimitiveDelta> primitiveDeltas = delta.split();
		if (primitiveDeltas.size() == 1) {
			return weavePrimitiveDelta(codebase, primitiveDeltas.getFirst());
		} else {
			// To do: Temporary implementation to be replaced with the actual implementation.
			for (PrimitiveDelta primDelta: primitiveDeltas) {
				weavePrimitiveDelta(codebase, primDelta);
			}
			return codebase;
		}
	}
	public static Codebase weavePrimitiveDelta(Codebase codebase, PrimitiveDelta primitiveDelta) {
		if (primitiveDelta instanceof PrimitivePullDelta) {
			codebase = weavePrimitivePullDelta(codebase, (PrimitivePullDelta) primitiveDelta);
		} else if (primitiveDelta instanceof PrimitivePullPushDelta) {
			codebase = weavePrimitivePullPushDelta(codebase, (PrimitivePullPushDelta) primitiveDelta);
		} else if (primitiveDelta instanceof PrimitivePushDelta) {
			codebase = weavePrimitivePushDelta(codebase, (PrimitivePushDelta) primitiveDelta);
		}
		return codebase;
	}

	private static Codebase weavePrimitivePullDelta(Codebase codebase, PrimitivePullDelta primitivePullDelta) {
		ReferenceEdge pullEdge1 = primitivePullDelta.getPullEdge1();
		ReferenceEdge pullEdge2 = primitivePullDelta.getPullEdge2();
		ObjectNode obj1 = (ObjectNode) pullEdge1.getSource();
		ObjectNode obj2 = (ObjectNode) pullEdge1.getDestination();
		ObjectNode obj3 = (ObjectNode) pullEdge2.getDestination();
		String name1 = toComponentName(obj1.getName());
		String name2 = toComponentName(obj2.getName());
		String name3 = toComponentName(obj3.getName());
		TypeDeclaration class1 = createClass(codebase, name1);
		TypeDeclaration class2 = createClass(codebase, name2);
		TypeDeclaration class3 = createClass(codebase, name3);
		models.algebra.Type type1 = codebase.getComponentType(name1);
		models.algebra.Type type2 = codebase.getComponentType(name2);
		models.algebra.Type type3 = codebase.getComponentType(name3);
		FieldDeclaration field12 = null;
		if (!pullEdge1.toMany()) {
			if (!pullEdge1.isIdentical()) {
				field12 = createField(class1, pullEdge1.getName(), type2);                              // class1 -> class2 (PULL)
			}
		} else {
			MapType typeKeyToType2 = codebase.getMapType(pullEdge1.getKeyTypeName(), name2);
			field12 = createField(class1, pullEdge1.getName() + mapPostfix, typeKeyToType2);	// class1 -> {class2} (PULL)
		}
		FieldDeclaration field23 = null;
		models.algebra.Type keyType = null;
		if (!pullEdge2.toMany()) {
			field23 = createField(class2, pullEdge2.getName(), type3);									// class2 -> class3 (PULL)
		} else {
			MapType typeKeyToType3 = codebase.getMapType(pullEdge2.getKeyTypeName(), name3);
			keyType = typeKeyToType3.getKeyType();
			field23 = createField(class2, pullEdge2.getName() + mapPostfix, typeKeyToType3);	// class2 -> {class3} (PULL)
		}
		FieldDeclaration field13 = null;
		if (!primitivePullDelta.isMultipleDefinition()) {
			field13 = createField(class1, pullEdge2.getName(), type3);                    				// class1 -> class3 (define)
		} else {
			String keyName = pullEdge2.getKeyTypeName();
			if (keyName == null) {
				keyName = pullEdge1.getKeyTypeName();
			}
			MapType typeKeyToType3 = codebase.getMapType(keyName, name3);
			keyType = typeKeyToType3.getKeyType();
			field13 = createField(class1, pullEdge2.getName() + mapPostfix, typeKeyToType3);   // class1 -> {class3} (define)
		}

		// Construct getter method
		MethodDeclaration getter = createMethod(class2, getterPrefix + toComponentName(pullEdge2.getName()));
		ReturnStatement return23 = new ReturnStatement();
		if (!pullEdge2.toMany()) {
			return23.setExpression(new FieldAccess(new ThisExpression(), field23.getName()));			// return this.name3;
		} else {
			VariableDeclaration idVar = new VariableDeclaration(keyType, pullEdge2.getName() + idPostfix);
			getter.addParameter(idVar);
			FieldAccess field3 = new FieldAccess(field23.getName());
			List<Expression> args = new ArrayList<>();
			args.add(new Variable(idVar.getName()));
			MethodInvocation callGetter = new MethodInvocation(field3, mapGet, args);					// name3Map.get(name3Id)
			return23.setExpression(callGetter);															// return name3Map.get(name3Id);
		}
		getter.addUniqueStatement(return23);
		getter.setReturnType(type3);

		// Construct coordinator method
		MethodDeclaration coordionator = null;
		if (primitivePullDelta.isMultipleDefinition() && !pullEdge1.toMany() && !primitivePullDelta.isMultipleTransfer()) {
			coordionator = createMethod(class1, addMethodPrefix + toComponentName(pullEdge2.getName()));
		} else {
			coordionator = createMethod(class1, updateMethodPrefix + toComponentName(pullEdge2.getName()));
		}
		FieldAccess field3 = new FieldAccess(new ThisExpression(), field13.getName());					// this.name3
		Expression field2 = null;
		if (field12 != null) {
			field2 = new FieldAccess(field12.getName());
		} else {
			field2 = new ThisExpression();
		}
		List<Expression> args = new ArrayList<>();
		if ((!primitivePullDelta.isMultipleDefinition() && pullEdge2.toMany())
			|| (primitivePullDelta.isMultipleDefinition() && !pullEdge1.toMany() && !primitivePullDelta.isMultipleTransfer())) {
			VariableDeclaration idVar = new VariableDeclaration(keyType, pullEdge2.getName() + idPostfix);
			coordionator.addParameter(idVar);
			args.add(new Variable(idVar.getName()));													// name2.getName3(name3Id)
		}
		MethodInvocation callGetter = new MethodInvocation(field2, getter.getName(), args);				// name2.getName3()
		if (!primitivePullDelta.isMultipleDefinition()) {
			Assignment assignment = new Assignment(field3, callGetter);                                 // this.name3 = name2.getName3();
			ExpressionStatement assignmentStatement = new ExpressionStatement(assignment);
			coordionator.addUniqueStatement(assignmentStatement);
		} else {
			if (!pullEdge1.toMany() && !primitivePullDelta.isMultipleTransfer()) {
				VariableDeclaration idVar = new VariableDeclaration(keyType, pullEdge2.getName() + idPostfix);
				args = new ArrayList<>();
				args.add(new Variable(idVar.getName()));
				args.add(callGetter);
				coordionator.addUniqueStatement(new ExpressionStatement(new MethodInvocation(field3, mapPut, args)));		// this.name3Map.put(name3Id, name2.getName3());
			} else {
				EnhancedForStatement collectionLoop = new EnhancedForStatement();											// for (key2 loopVar: name2Map.keySet())
				String loopVarName = null;
				if (pullEdge1.toMany()) {
					loopVarName = pullEdge1.getName() + idPostfix;
					if (field12 != null) {
						keyType = ((MapType) field12.getType()).getKeyType();
					}
				} else {
					loopVarName = pullEdge2.getName() + idPostfix;
					keyType = ((MapType) field13.getType()).getKeyType();
				}
				VariableDeclaration loopVar = new VariableDeclaration(keyType, loopVarName);
				MethodInvocation keysGetter = null;
				if (pullEdge1.toMany()) {
					keysGetter = new MethodInvocation(field2, mapKeySet);
				} else {
					keysGetter = new MethodInvocation(field3, mapKeySet);
				}
				args = new ArrayList<>();
				args.add(new Variable(loopVarName));
				if (pullEdge1.toMany()) {
					MethodInvocation valueGetter = new MethodInvocation(field2, mapGet, args);
					args = new ArrayList<>();
					args.add(new Variable(loopVarName));
					args.add(new MethodInvocation(valueGetter, getter.getName()));
				} else {
					args = new ArrayList<>();
					args.add(new Variable(loopVarName));
					args.add(new MethodInvocation(field2, getter.getName(), List.of(new Variable(loopVarName))));
				}
				Block loopBody = new Block();
				loopBody.addStatement(new ExpressionStatement(new MethodInvocation(field3, mapPut, args)));		// this.name3Map.put(loopVar, name2Map.get(loopVar).getName3());
				collectionLoop.setParameter(loopVar);
				collectionLoop.setExpression(keysGetter);
				collectionLoop.setBody(loopBody);
				coordionator.addUniqueStatement(collectionLoop);
			}
		}
		return codebase;
	}

	private static Codebase weavePrimitivePullPushDelta(Codebase codebase, PrimitivePullPushDelta primitivePullPushDelta) {
		ReferenceEdge pullEdge = primitivePullPushDelta.getPullEdge();
		ReferenceEdge pushEdge = primitivePullPushDelta.getPushEdge();
		ObjectNode obj1 = (ObjectNode) pullEdge.getSource();
		ObjectNode obj2 = (ObjectNode) pullEdge.getDestination();
		ObjectNode obj3 = (ObjectNode) pushEdge.getDestination();
		String name1 = toComponentName(obj1.getName());
		String name2 = toComponentName(obj2.getName());
		String name3 = toComponentName(obj3.getName());
		TypeDeclaration class1 = createClass(codebase, name1);
		TypeDeclaration class2 = createClass(codebase, name2);
		TypeDeclaration class3 = createClass(codebase, name3);
		models.algebra.Type type1 = codebase.getComponentType(name1);
		models.algebra.Type type2 = codebase.getComponentType(name2);
		models.algebra.Type type3 = codebase.getComponentType(name3);
		FieldDeclaration field12 = null;
		if (!pullEdge.toMany()) {
			field12 = createField(class1, pullEdge.getName(), type2);									// class1 -> class2 (PULL)
		} else {
			MapType typeKeyToType2 = codebase.getMapType(pullEdge.getKeyTypeName(), name2);
			field12 = createField(class1, pullEdge.getName() + mapPostfix, typeKeyToType2);    // class3 -> {class2} (PULL)
		}
		FieldDeclaration field13 = null;
		models.algebra.Type keyType = null;
		models.algebra.Type keyType2 = null;
		if (!pushEdge.toMany()) {
			field13 = createField(class1, pushEdge.getName(), type3);									// class1 -> class3 (PUSH)
		} else {
			MapType typeKeyToType3 = codebase.getMapType(pushEdge.getKeyTypeName(), name3);
			keyType = typeKeyToType3.getKeyType();
			field13 = createField(class1, pushEdge.getName() + mapPostfix, typeKeyToType3);	// class1 -> {class3} (PUSH)
		}
		FieldDeclaration field32 = null;
		if (!primitivePullPushDelta.isMultipleDefinition()) {
			field32 = createField(class3, pullEdge.getName(), type2);                                   // class3 -> class2 (define)
		} else {
			MapType typeKeyToType2 = codebase.getMapType(pullEdge.getKeyTypeName(), name2);
			keyType2 = typeKeyToType2.getKeyType();
			field32 = createField(class3, pullEdge.getName() + mapPostfix, typeKeyToType2);    // class3 -> {class2} (define)
		}

		// Construct setter method
		MethodDeclaration setter = null;
		VariableDeclaration param2Id = null;
		if (pushEdge.toMany() && !primitivePullPushDelta.isMultipleDefinition() && !primitivePullPushDelta.isMultipleTransfer()) {
			setter = createConstructor(class3);
		} else {
			setter = createMethod(class3, setterPrefix + toComponentName(name2));
			if (primitivePullPushDelta.isMultipleDefinition()) {
				param2Id = new VariableDeclaration(keyType2, toVariableName(name2) + idPostfix);
				setter.addParameter(param2Id);
			}
		}
		VariableDeclaration param2 = new VariableDeclaration(type2, toVariableName(pullEdge.getName()));
		setter.addParameter(param2);
		FieldAccess field2 = new FieldAccess(new ThisExpression(), field32.getName());
		if (primitivePullPushDelta.isMultipleDefinition()) {
			List<Expression> args = new ArrayList<>();
			args.add(new Variable(param2Id.getName()));
			args.add(new Variable(toVariableName(name2)));
			setter.addUniqueStatement(new ExpressionStatement(new MethodInvocation(field2, mapPut, args)));	// name2Map.put(name2Id, name2);
		} else {
			Assignment assignment = new Assignment(field2, new Variable(toVariableName(pullEdge.getName())));			// this.name2 = name2;
			ExpressionStatement assignmentStatement = new ExpressionStatement(assignment);
			setter.addUniqueStatement(assignmentStatement);
		}

		// Construct coordinator method
		MethodDeclaration coordionator = null;
		if (pushEdge.toMany() && !primitivePullPushDelta.isMultipleDefinition() && !primitivePullPushDelta.isMultipleTransfer()) {
			coordionator = createMethod(class1, addMethodPrefix + toComponentName(name3));
		} else {
			coordionator = createMethod(class1, updateMethodPrefix + toComponentName(name3));
		}
		FieldAccess field3 = new FieldAccess(field13.getName());
		if (!pushEdge.toMany() || primitivePullPushDelta.isMultipleDefinition() || primitivePullPushDelta.isMultipleTransfer()) {
			if (!primitivePullPushDelta.isMultipleDefinition() && !primitivePullPushDelta.isMultipleTransfer()) {
				List<Expression> args = new ArrayList<>();
				args.add(new FieldAccess(field12.getName()));
				MethodInvocation callSetter = new MethodInvocation(field3, setter.getName(), args);     // name3.setName2(name2)
				coordionator.addUniqueStatement(new ExpressionStatement(callSetter));
			} else {
				EnhancedForStatement broadcastLoop = new EnhancedForStatement();						// for (key2 loopVar: name2Map.keySet())
				MethodInvocation callSetter = null;
				MethodInvocation keysGetter = null;
				String loopVarName = null;
				List<Expression> args = new ArrayList<>();
				if (pullEdge.toMany()) {
					loopVarName = pullEdge.getName() + idPostfix;
					keyType = ((MapType) field12.getType()).getKeyType();
					args.add(new Variable(loopVarName));
					MethodInvocation valueGetter = new MethodInvocation(field2, mapGet, args);
					args = new ArrayList<>();
					args.add(new Variable(loopVarName));
					args.add(valueGetter);
					callSetter = new MethodInvocation(field3, setter.getName(), args);      // name3.setName2(loopVar, name2Map.get(loopVar))
					keysGetter = new MethodInvocation(field2, mapKeySet);
				} else {
					loopVarName = pushEdge.getName() + idPostfix;
					keyType = ((MapType) field13.getType()).getKeyType();
					args.add(new Variable(loopVarName));
					MethodInvocation valueGetter = new MethodInvocation(field3, mapGet, args);
					args = new ArrayList<>();
					args.add(field2);
					callSetter = new MethodInvocation(valueGetter, setter.getName(), args);      // name3Map.get(loopVar).setName2(name2)
					keysGetter = new MethodInvocation(field3, mapKeySet);
				}
				Block loopBody = new Block();
				loopBody.addStatement(new ExpressionStatement(callSetter));
				VariableDeclaration loopVar = new VariableDeclaration(keyType, loopVarName);
				broadcastLoop.setParameter(loopVar);
				broadcastLoop.setExpression(keysGetter);
				broadcastLoop.setBody(loopBody);
				coordionator.addUniqueStatement(broadcastLoop);
			}
		} else {
			VariableDeclaration idVar = new VariableDeclaration(keyType, pushEdge.getName() + idPostfix);
			coordionator.addParameter(idVar);
			List<Expression> args = new ArrayList<>();
			args.add(new FieldAccess(field12.getName()));
			ClassInstanceCreation callConstructor = new ClassInstanceCreation((SimpleType) type3.getImplementationType(), args);	// new Name3(name2)
			args = new ArrayList<>();
			args.add(new Variable(idVar.getName()));
			args.add(callConstructor);
			coordionator.addUniqueStatement(new ExpressionStatement(new MethodInvocation(field3, mapPut, args)));		// name3Map.put(name3Id, new new Name3(name2));
		}
		return codebase;
	}

	private static Codebase weavePrimitivePushDelta(Codebase codebase, PrimitivePushDelta primitivePushDelta) {
		ReferenceEdge pushEdge1 = primitivePushDelta.getPushEdge1();
		ReferenceEdge pushEdge2 = primitivePushDelta.getPushEdge2();
		ObjectNode obj1 = (ObjectNode) pushEdge1.getSource();
		ObjectNode obj2 = (ObjectNode) pushEdge1.getDestination();
		ObjectNode obj3 = (ObjectNode) pushEdge2.getDestination();
		String name1 = toComponentName(obj1.getName());
		String name2 = toComponentName(obj2.getName());
		String name3 = toComponentName(obj3.getName());
		TypeDeclaration class1 = createClass(codebase, name1);
		TypeDeclaration class2 = createClass(codebase, name2);
		TypeDeclaration class3 = createClass(codebase, name3);
		models.algebra.Type type1 = codebase.getComponentType(name1);
		models.algebra.Type type2 = codebase.getComponentType(name2);
		models.algebra.Type type3 = codebase.getComponentType(name3);
		FieldDeclaration field12 = createField(class1, pushEdge1.getName(), type2);					// class1 -> class2 (PUSH)
		FieldDeclaration field23 = createField(class2, pushEdge2.getName(), type3);					// class2 -> class3 (PUSH)
		FieldDeclaration field31 = createField(class3, toVariableName(name1), type1);				// class3 -> class1 (create)

		// Construct setter in class3
		MethodDeclaration setter3 = createMethod(class3, setterPrefix + toComponentName(name1));
		VariableDeclaration param1 = new VariableDeclaration(type1, name1);
		setter3.addParameter(param1);
		FieldAccess field1 = new FieldAccess(new ThisExpression(), field31.getName());				// this.name1
		Assignment assignment = new Assignment(field1, new Variable(toVariableName(name1)));		// this.name1 = name1;
		ExpressionStatement assignmentStatement = new ExpressionStatement(assignment);
		setter3.addUniqueStatement(assignmentStatement);

		// Construct setter in class2
		MethodDeclaration setter2 = createMethod(class2, setterPrefix + toComponentName(name1));
		param1 = new VariableDeclaration(type1, toVariableName(name1));
		setter2.addParameter(param1);
		FieldAccess field3 = new FieldAccess(field23.getName());
		List<Expression> args = new ArrayList<>();
		args.add(new Variable(param1.getName()));
		MethodInvocation callSetter = new MethodInvocation(field3, setter3.getName(), args);		// name3.setName1(name1)
		setter2.addUniqueStatement(new ExpressionStatement(callSetter));

		// Construct coordinator method
		MethodDeclaration coordionator = createMethod(class1, updateMethodPrefix + toComponentName(name1));
		FieldAccess field2 = new FieldAccess(field12.getName());
		args = new ArrayList<>();
		args.add(new ThisExpression());
		callSetter = new MethodInvocation(field2, setter2.getName(), args);							// name2.setName1(this)
		coordionator.addUniqueStatement(new ExpressionStatement(callSetter));
		return codebase;
	}

	public static Codebase weaveMediatorInsertion(Codebase codebase, MediatorInsertion mediatorInsertion) {
		ObjectNode srcNode, dstNode;
		if (mediatorInsertion.getPushPullValue() == TransferStyle.PUSH) {
			srcNode = mediatorInsertion.getSrc();
			dstNode = mediatorInsertion.getDst();
		} else {
			srcNode = mediatorInsertion.getDst();
			dstNode = mediatorInsertion.getSrc();
		}
		return MediatorInsertionWeaver.weave(codebase, mediatorInsertion, srcNode, dstNode);
	}

	private static TypeDeclaration createClass(Codebase codebase, String name) {
		if (codebase.getCompilationUnit(name) != null) return codebase.getCompilationUnit(name).types().getFirst();
		TypeDeclaration type = new TypeDeclaration(name);
		CompilationUnit compilationUnit = new CompilationUnit(type);
		codebase.addCompilationUnit(name, compilationUnit);
		return type;
	}

	private static MethodDeclaration createMethod(TypeDeclaration type, String name) {
		for (MethodDeclaration method: type.getMethods()) {
			if (method.getName().equals(name)) return method;
		}
		MethodDeclaration method = new MethodDeclaration(name);
		type.addMethod(method);
		return method;
	}

	private static MethodDeclaration createConstructor(TypeDeclaration type) {
		for (MethodDeclaration method: type.getMethods()) {
			if (method.getName().equals(type.getTypeName())) return method;
		}
		MethodDeclaration method = new MethodDeclaration(type.getTypeName(), true);
		type.addMethod(method);
		return method;
	}

	private static FieldDeclaration createField(TypeDeclaration type, String fieldName, models.algebra.Type fieldType) {
		for (FieldDeclaration field: type.getFields()) {
			if (field.getName().equals(fieldName)) return field;
		}
		FieldDeclaration field = new FieldDeclaration(fieldType, fieldName);
		type.addField(field);
		return field;
	}
}