package generators;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import ast.*;
import designPatternExtensions.DependencyInversion;
import designPatternExtensions.InterfaceNode;
import designPatternExtensions.MediatorInsertion;
import designPatternExtensions.PresenterInsertion;
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()) {
codebase = weaveMediatorInsertion(codebase, mi);
}
for (PresenterInsertion pi: dataSynchronizationDesign.getPresenterInsertions()) {
codebase = weavePresenterInsertion(codebase, pi);
}
for (DependencyInversion di: dataSynchronizationDesign.getDependencyInversions()) {
codebase = weaveDependencyInversion(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) {
TransferStyle style = mediatorInsertion.getPushPullValue();
ObjectNode srcNode, dstNode;
if (style == TransferStyle.PUSH) {
srcNode = mediatorInsertion.getSrc();
dstNode = mediatorInsertion.getDst();
} else {
srcNode = mediatorInsertion.getDst();
dstNode = mediatorInsertion.getSrc();
}
ObjectNode mediatorNode = mediatorInsertion.getMediator();
String srcName = toComponentName(srcNode.getName());
String dstName = toComponentName(dstNode.getName());
String mediatorName = toComponentName(mediatorNode.getName());
String mediatorFieldName = toVariableName(mediatorName);
String dstFieldName = toVariableName(dstName);
models.algebra.Type dstType = codebase.getComponentType(dstName);
TypeDeclaration mediatorClass = createClass(codebase, mediatorName);
models.algebra.Type mediatorType = codebase.getComponentType(mediatorName);
TypeDeclaration srcClass = createClass(codebase, srcName);
TypeDeclaration dstClass = createClass(codebase, dstName);
FieldDeclaration srcDstMapField = null;
for (FieldDeclaration field: srcClass.getFields()) {
if (field.getType() instanceof MapType) {
MapType mapType = (MapType) field.getType();
if (mapType.getValueType() != null && mapType.getValueType().getTypeName().equals(dstType.getTypeName())) {
srcDstMapField = field;
break;
}
}
}
boolean toMany = srcDstMapField != null;
FieldDeclaration dstField;
if (toMany) {
MapType srcMapType = (MapType) srcDstMapField.getType();
FieldDeclaration existingMediatorMapField = null;
for (FieldDeclaration field: mediatorClass.getFields()) {
if (field.getType() instanceof MapType) {
MapType mapType = (MapType) field.getType();
if (mapType.getValueType() != null && mapType.getValueType().getTypeName().equals(dstType.getTypeName())
&& mapType.getKeyType().getTypeName().equals(srcMapType.getKeyType().getTypeName())) {
existingMediatorMapField = field;
break;
}
}
}
dstField = (existingMediatorMapField != null)
? existingMediatorMapField
: createField(mediatorClass, srcDstMapField.getName(), srcMapType);
} else {
dstField = createField(mediatorClass, dstFieldName, dstType);
}
// Add Constructor
MethodDeclaration constructor = createConstructor(mediatorClass);
if (!hasParameterNamed(constructor, dstField.getName())) {
VariableDeclaration dstParam = new VariableDeclaration(dstField.getType(), dstField.getName());
constructor.addParameter(dstParam);
FieldAccess dstFieldAccess = new FieldAccess(new ThisExpression(), dstField.getName());
Assignment dstAssignment = new Assignment(dstFieldAccess, new Variable(dstField.getName()));
constructor.addUniqueStatement(new ExpressionStatement(dstAssignment));
}
if (!toMany) {
for (MethodDeclaration method: dstClass.getMethods()) {
if (method.isConstructor()) continue;
MethodDeclaration delegateMethod = createMethod(mediatorClass, method.getName());
delegateMethod.setReturnType(method.getReturnType());
List<Expression> args = new ArrayList<>();
if (method.getParameters() != null) {
for (VariableDeclaration param: method.getParameters()) {
delegateMethod.addParameter(param);
args.add(new Variable(param.getName()));
}
}
FieldAccess dstAccess = new FieldAccess(dstField.getName());
MethodInvocation callDst = new MethodInvocation(dstAccess, method.getName(), args);
if (method.getReturnType() != null) {
ReturnStatement returnStatement = new ReturnStatement();
returnStatement.setExpression(callDst);
delegateMethod.addUniqueStatement(returnStatement);
} else {
delegateMethod.addUniqueStatement(new ExpressionStatement(callDst));
}
}
}
FieldDeclaration srcFieldToReplace = toMany ? srcDstMapField : null;
if (!toMany) {
for (FieldDeclaration field: srcClass.getFields()) {
if (field.getType() != null && field.getType().getTypeName().equals(dstType.getTypeName())) {
srcFieldToReplace = field;
break;
}
}
}
if (srcFieldToReplace != null) {
FieldDeclaration existingMediatorField = null;
for (FieldDeclaration field: srcClass.getFields()) {
if (field != srcFieldToReplace && field.getType() != null
&& field.getType().getTypeName().equals(mediatorType.getTypeName())) {
existingMediatorField = field;
break;
}
}
String oldFieldName = srcFieldToReplace.getName();
if (existingMediatorField != null) {
srcClass.removeField(srcFieldToReplace);
mediatorFieldName = existingMediatorField.getName();
} else {
srcFieldToReplace.setType(mediatorType);
srcFieldToReplace.setName(mediatorFieldName);
}
// Add Mediator Setter
MethodDeclaration mediatorSetter = createMethod(srcClass, setterPrefix + mediatorName);
if (mediatorSetter.getParameters() == null || mediatorSetter.getParameters().isEmpty()) {
VariableDeclaration mediatorParam = new VariableDeclaration(mediatorType, mediatorFieldName);
mediatorSetter.addParameter(mediatorParam);
FieldAccess mediatorFieldAccess = new FieldAccess(new ThisExpression(), mediatorFieldName);
Assignment mediatorAssignment = new Assignment(mediatorFieldAccess, new Variable(mediatorFieldName));
mediatorSetter.addUniqueStatement(new ExpressionStatement(mediatorAssignment));
}
// Change the constructor parameter type in src from dst to Mediator, and rename the parameter accordingly
for (MethodDeclaration method: srcClass.getMethods()) {
if (method.isConstructor() && method.getParameters() != null) {
for (VariableDeclaration param: method.getParameters()) {
if (param.getType() != null && param.getType().getTypeName().equals(dstType.getTypeName())) {
param.setType(mediatorType);
param.setName(mediatorFieldName);
}
}
}
}
List<MethodDeclaration> methodsToMove = new ArrayList<>();
for (MethodDeclaration method: srcClass.getMethods()) {
if (method.isConstructor()) continue;
if (methodReferencesField(method, oldFieldName)) {
methodsToMove.add(method);
}
}
for (MethodDeclaration method: methodsToMove) {
srcClass.removeMethod(method);
List<FieldDeclaration> danglingFields = new ArrayList<>();
if (method.getBody() != null) {
for (Statement statement: method.getBody().getStatements2()) {
collectDanglingValueFields(statement, srcClass, mediatorClass, oldFieldName, danglingFields);
}
}
Set<String> danglingFieldNames = new HashSet<>();
for (FieldDeclaration extra: danglingFields) {
danglingFieldNames.add(extra.getName());
if (!hasParameterNamed(method, extra.getName())) {
method.addParameter(new VariableDeclaration(extra.getType(), extra.getName()));
}
}
if (!danglingFieldNames.isEmpty() && method.getBody() != null) {
for (Statement statement: method.getBody().getStatements2()) {
rewriteDanglingFieldsAsParams(statement, danglingFieldNames);
}
}
MethodDeclaration existingOnMediator = null;
for (MethodDeclaration existing: mediatorClass.getMethods()) {
if (existing.getName().equals(method.getName())) { existingOnMediator = existing; break; }
}
if (existingOnMediator == null) {
mediatorClass.addMethod(method);
}
MethodDeclaration stub = createMethod(srcClass, method.getName());
stub.setReturnType(method.getReturnType());
List<Expression> args = new ArrayList<>();
if (method.getParameters() != null) {
for (VariableDeclaration param: method.getParameters()) {
if (danglingFieldNames.contains(param.getName())) {
args.add(new FieldAccess(param.getName()));
} else {
stub.addParameter(param);
args.add(new Variable(param.getName()));
}
}
}
FieldAccess mediatorAccess = new FieldAccess(mediatorFieldName);
MethodInvocation callMediator = new MethodInvocation(mediatorAccess, method.getName(), args);
if (method.getReturnType() != null) {
ReturnStatement returnStatement = new ReturnStatement();
returnStatement.setExpression(callMediator);
stub.addUniqueStatement(returnStatement);
} else {
stub.addUniqueStatement(new ExpressionStatement(callMediator));
}
}
for (MethodDeclaration method: srcClass.getMethods()) {
if (method.getBody() == null) continue;
for (Statement statement: method.getBody().getStatements2()) {
replaceFieldNameInStatement(statement, oldFieldName, mediatorFieldName);
}
}
}
return codebase;
}
private static void replaceFieldNameInStatement(Statement statement, String oldName, String newName) {
if (statement instanceof ExpressionStatement) {
replaceFieldNameInExpression(((ExpressionStatement) statement).getExpression(), oldName, newName);
}
}
private static void replaceFieldNameInExpression(Expression expr, String oldName, String newName) {
if (expr instanceof MethodInvocation) {
MethodInvocation mi = (MethodInvocation) expr;
if (mi.getReceiver() instanceof FieldAccess) {
FieldAccess fa = (FieldAccess) mi.getReceiver();
if (fa.getFieldName().equals(oldName)) {
fa.setFieldName(newName);
}
}
for (Expression arg: mi.getArguments()) {
replaceFieldNameInExpression(arg, oldName, newName);
}
} else if (expr instanceof Assignment) {
Assignment assignment = (Assignment) expr;
if (assignment.getLeft() instanceof FieldAccess) {
FieldAccess fa = (FieldAccess) assignment.getLeft();
if (fa.getFieldName().equals(oldName)) {
fa.setFieldName(newName);
}
}
if (assignment.getRight() instanceof Variable) {
Variable var = (Variable) assignment.getRight();
if (var.getName().equals(oldName)) {
assignment.setRight(new Variable(newName));
}
}
replaceFieldNameInExpression(assignment.getLeft(), oldName, newName);
replaceFieldNameInExpression(assignment.getRight(), oldName, newName);
}
}
public static Codebase weavePresenterInsertion(Codebase codebase, PresenterInsertion presenterInsertion) {
ObjectNode srcNode = presenterInsertion.getSrc();
ObjectNode dstNode = presenterInsertion.getDst();
ObjectNode presenterNode = presenterInsertion.getPresenter();
String srcName = toComponentName(srcNode.getName());
String dstName = toComponentName(dstNode.getName());
String presenterName = toComponentName(presenterNode.getName());
String srcFieldName = toVariableName(srcName);
String dstFieldName = toVariableName(dstName);
models.algebra.Type srcType = codebase.getComponentType(srcName);
models.algebra.Type dstType = codebase.getComponentType(dstName);
TypeDeclaration presenterClass = createClass(codebase, presenterName);
FieldDeclaration srcField = createField(presenterClass, srcFieldName, srcType);
FieldDeclaration dstField = createField(presenterClass, dstFieldName, dstType);
MethodDeclaration constructor = createConstructor(presenterClass);
if (!hasParameterNamed(constructor, srcFieldName)) {
constructor.addParameter(new VariableDeclaration(srcType, srcFieldName));
constructor.addUniqueStatement(new ExpressionStatement(
new Assignment(new FieldAccess(new ThisExpression(), srcField.getName()), new Variable(srcFieldName))));
}
if (!hasParameterNamed(constructor, dstFieldName)) {
constructor.addParameter(new VariableDeclaration(dstType, dstFieldName));
constructor.addUniqueStatement(new ExpressionStatement(
new Assignment(new FieldAccess(new ThisExpression(), dstField.getName()), new Variable(dstFieldName))));
}
TypeDeclaration srcClass = createClass(codebase, srcName);
TypeDeclaration dstClass = createClass(codebase, dstName);
List<MethodDeclaration> getters = new ArrayList<>();
List<String> boundProperties = new ArrayList<>();
for (MethodDeclaration getter: srcClass.getMethods()) {
if (getter.isConstructor() || getter.getReturnType() == null) continue;
if (!getter.getName().startsWith(getterPrefix)) continue;
getters.add(getter);
boundProperties.add(getter.getName().substring(getterPrefix.length()));
}
for (FieldDeclaration field: srcClass.getFields()) {
if (field.getType() == null) continue;
if (field.getType().getTypeName().equals(dstType.getTypeName())) continue;
String propertyName = toComponentName(field.getType().getTypeName());
if (boundProperties.contains(propertyName)) continue;
MethodDeclaration newGetter = createMethod(srcClass, getterPrefix + propertyName);
if (newGetter.getReturnType() == null) {
ReturnStatement returnStatement = new ReturnStatement();
returnStatement.setExpression(new FieldAccess(new ThisExpression(), field.getName()));
newGetter.addUniqueStatement(returnStatement);
newGetter.setReturnType(field.getType());
}
getters.add(newGetter);
boundProperties.add(propertyName);
}
for (MethodDeclaration getter: getters) {
String propertyName = getter.getName().substring(getterPrefix.length());
String setterName = setterPrefix + propertyName;
MethodDeclaration setter = createMethod(dstClass, setterName);
if (setter.getReturnType() == null && (setter.getParameters() == null || setter.getParameters().isEmpty())) {
models.algebra.Type propertyType = getter.getReturnType();
VariableDeclaration setterParam = new VariableDeclaration(propertyType, toVariableName(propertyName));
setter.addParameter(setterParam);
FieldDeclaration propertyField = createField(dstClass, toVariableName(propertyName), propertyType);
setter.addUniqueStatement(new ExpressionStatement(
new Assignment(new FieldAccess(new ThisExpression(), propertyField.getName()), new Variable(toVariableName(propertyName)))));
}
String presenterMethodName = updateMethodPrefix + dstName + propertyName;
MethodDeclaration coordinator = createMethod(presenterClass, presenterMethodName);
if (coordinator.getBody() == null || coordinator.getBody().getStatements2().isEmpty()) {
List<Expression> getterArgs = new ArrayList<>();
MethodInvocation callGetter = new MethodInvocation(new FieldAccess(srcField.getName()), getter.getName(), getterArgs);
List<Expression> setterArgs = new ArrayList<>();
setterArgs.add(callGetter);
MethodInvocation callSetter = new MethodInvocation(new FieldAccess(dstField.getName()), setter.getName(), setterArgs);
coordinator.addUniqueStatement(new ExpressionStatement(callSetter));
}
}
if (presenterInsertion.getRemoveDirectDependency()) {
if (presenterInsertion.getPushPullValue() == TransferStyle.PUSH) {
removeDirectDependency(srcClass, dstType);
} else {
removeDirectDependency(dstClass, srcType);
}
}
return codebase;
}
private static boolean hasParameterNamed(MethodDeclaration method, String name) {
if (method.getParameters() == null) return false;
for (VariableDeclaration param: method.getParameters()) {
if (param.getName().equals(name)) return true;
}
return false;
}
private static void removeDirectDependency(TypeDeclaration typeDecl, models.algebra.Type otherType) {
List<FieldDeclaration> fieldsToRemove = new ArrayList<>();
for (FieldDeclaration field: typeDecl.getFields()) {
if (field.getType() != null && field.getType().getTypeName().equals(otherType.getTypeName())) {
fieldsToRemove.add(field);
}
}
if (fieldsToRemove.isEmpty()) return;
List<MethodDeclaration> methodsToRemove = new ArrayList<>();
for (MethodDeclaration method: typeDecl.getMethods()) {
if (method.isConstructor()) continue;
for (FieldDeclaration field: fieldsToRemove) {
if (methodReferencesField(method, field.getName())) {
methodsToRemove.add(method);
break;
}
}
}
for (MethodDeclaration method: methodsToRemove) {
typeDecl.removeMethod(method);
}
for (FieldDeclaration field: fieldsToRemove) {
typeDecl.removeField(field);
}
}
private static boolean methodReferencesField(MethodDeclaration method, String fieldName) {
if (method.getBody() == null) return false;
for (Statement statement: method.getBody().getStatements2()) {
if (statementReferencesField(statement, fieldName)) return true;
}
return false;
}
private static boolean statementReferencesField(Statement statement, String fieldName) {
if (statement instanceof ExpressionStatement) {
return expressionReferencesField(((ExpressionStatement) statement).getExpression(), fieldName);
} else if (statement instanceof ReturnStatement) {
Expression expr = ((ReturnStatement) statement).getExpression();
return expr != null && expressionReferencesField(expr, fieldName);
} else if (statement instanceof EnhancedForStatement) {
EnhancedForStatement forStatement = (EnhancedForStatement) statement;
if (expressionReferencesField(forStatement.getExpression(), fieldName)) return true;
Statement body = forStatement.getBody();
if (body == null) return false;
if (body instanceof Block) {
for (Statement inner: ((Block) body).getStatements2()) {
if (statementReferencesField(inner, fieldName)) return true;
}
return false;
}
return statementReferencesField(body, fieldName);
}
return false;
}
private static void collectDanglingValueFields(Statement statement, TypeDeclaration srcClass,
TypeDeclaration mediatorClass, String movedFieldName, List<FieldDeclaration> out) {
if (statement instanceof ExpressionStatement) {
walkExpressionForDanglingFields(((ExpressionStatement) statement).getExpression(), false, srcClass, mediatorClass, movedFieldName, out);
} else if (statement instanceof ReturnStatement) {
walkExpressionForDanglingFields(((ReturnStatement) statement).getExpression(), true, srcClass, mediatorClass, movedFieldName, out);
} else if (statement instanceof EnhancedForStatement) {
EnhancedForStatement forStatement = (EnhancedForStatement) statement;
walkExpressionForDanglingFields(forStatement.getExpression(), false, srcClass, mediatorClass, movedFieldName, out);
Statement body = forStatement.getBody();
if (body instanceof Block) {
for (Statement inner: ((Block) body).getStatements2()) {
collectDanglingValueFields(inner, srcClass, mediatorClass, movedFieldName, out);
}
} else if (body != null) {
collectDanglingValueFields(body, srcClass, mediatorClass, movedFieldName, out);
}
}
}
private static void walkExpressionForDanglingFields(Expression expr, boolean asValue, TypeDeclaration srcClass,
TypeDeclaration mediatorClass, String movedFieldName, List<FieldDeclaration> out) {
if (expr == null) return;
if (expr instanceof FieldAccess) {
if (!asValue) return;
String name = ((FieldAccess) expr).getFieldName();
if (name.equals(movedFieldName)) return;
for (FieldDeclaration f: mediatorClass.getFields()) if (f.getName().equals(name)) return;
for (FieldDeclaration f: out) if (f.getName().equals(name)) return;
for (FieldDeclaration f: srcClass.getFields()) {
if (f.getName().equals(name)) { out.add(f); return; }
}
} else if (expr instanceof MethodInvocation) {
MethodInvocation mi = (MethodInvocation) expr;
walkExpressionForDanglingFields(mi.getReceiver(), false, srcClass, mediatorClass, movedFieldName, out);
if (mi.getArguments() != null) {
for (Expression arg: mi.getArguments()) {
walkExpressionForDanglingFields(arg, true, srcClass, mediatorClass, movedFieldName, out);
}
}
} else if (expr instanceof Assignment) {
Assignment assignment = (Assignment) expr;
walkExpressionForDanglingFields(assignment.getLeft(), false, srcClass, mediatorClass, movedFieldName, out);
walkExpressionForDanglingFields(assignment.getRight(), true, srcClass, mediatorClass, movedFieldName, out);
}
}
private static void rewriteDanglingFieldsAsParams(Statement statement, Set<String> fieldNames) {
if (statement instanceof ExpressionStatement) {
rewriteExpressionValues(((ExpressionStatement) statement).getExpression(), false, fieldNames);
} else if (statement instanceof ReturnStatement) {
ReturnStatement rs = (ReturnStatement) statement;
Expression expr = rs.getExpression();
if (expr instanceof FieldAccess && fieldNames.contains(((FieldAccess) expr).getFieldName())) {
rs.setExpression(new Variable(((FieldAccess) expr).getFieldName()));
} else {
rewriteExpressionValues(expr, true, fieldNames);
}
} else if (statement instanceof EnhancedForStatement) {
EnhancedForStatement forStatement = (EnhancedForStatement) statement;
Statement body = forStatement.getBody();
if (body instanceof Block) {
for (Statement inner: ((Block) body).getStatements2()) {
rewriteDanglingFieldsAsParams(inner, fieldNames);
}
} else if (body != null) {
rewriteDanglingFieldsAsParams(body, fieldNames);
}
}
}
private static void rewriteExpressionValues(Expression expr, boolean asValue, Set<String> fieldNames) {
if (expr instanceof MethodInvocation) {
MethodInvocation mi = (MethodInvocation) expr;
rewriteExpressionValues(mi.getReceiver(), false, fieldNames);
if (mi.getArguments() != null) {
List<Expression> args = mi.getArguments();
for (int i = 0; i < args.size(); i++) {
Expression arg = args.get(i);
if (arg instanceof FieldAccess && fieldNames.contains(((FieldAccess) arg).getFieldName())) {
args.set(i, new Variable(((FieldAccess) arg).getFieldName()));
} else {
rewriteExpressionValues(arg, true, fieldNames);
}
}
}
} else if (expr instanceof Assignment) {
Assignment assignment = (Assignment) expr;
rewriteExpressionValues(assignment.getLeft(), false, fieldNames);
Expression right = assignment.getRight();
if (right instanceof FieldAccess && fieldNames.contains(((FieldAccess) right).getFieldName())) {
assignment.setRight(new Variable(((FieldAccess) right).getFieldName()));
} else {
rewriteExpressionValues(right, true, fieldNames);
}
}
}
private static boolean expressionReferencesField(Expression expr, String fieldName) {
if (expr == null) return false;
if (expr instanceof FieldAccess) {
FieldAccess fa = (FieldAccess) expr;
if (fieldName.equals(fa.getFieldName())) return true;
}
if (expr instanceof MethodInvocation) {
MethodInvocation mi = (MethodInvocation) expr;
if (expressionReferencesField(mi.getReceiver(), fieldName)) return true;
if (mi.getArguments() != null) {
for (Expression arg: mi.getArguments()) {
if (expressionReferencesField(arg, fieldName)) return true;
}
}
} else if (expr instanceof Assignment) {
Assignment assignment = (Assignment) expr;
if (expressionReferencesField(assignment.getLeft(), fieldName)) return true;
if (expressionReferencesField(assignment.getRight(), fieldName)) return true;
}
return false;
}
public static Codebase weaveDependencyInversion(Codebase codebase, DependencyInversion dependencyInversion) {
ObjectNode dstNode = dependencyInversion.getDst();
InterfaceNode interfaceNode = dependencyInversion.getInterfaceNode();
String dstName = toComponentName(dstNode.getName());
String interfaceName = toComponentName(interfaceNode.getName());
//print -> public interface interfaceName { ... }
InterfaceDeclaration interfaceDeclaration = DependencyInversionWeaver.createInterface(codebase, interfaceName);
TypeDeclaration dstClass = createClass(codebase, dstName);
for (MethodDeclaration method: dstClass.getMethods()) {
if (!method.isConstructor()) {
boolean alreadyExists = interfaceDeclaration.getMethods().stream()
.anyMatch(m -> m.getName().equals(method.getName()));
if (!alreadyExists) {
interfaceDeclaration.addMethod(method);
}
}
}
// public class "dstClass" implements "interfaceName"
if (!dstClass.getImplementsInterfaces().contains(interfaceName)) {
dstClass.addImplementsInterface(interfaceName);
}
models.algebra.Type interfaceType = codebase.getComponentType(interfaceName);
models.algebra.Type dstType = codebase.getComponentType(dstName);
// // Change dst class method return types
DependencyInversionWeaver.replaceDepedencyInClass(codebase, dstClass, dstType, interfaceType, interfaceName);
if (dependencyInversion.getTargetSrc() != null) {
// Traverse only the specified target class
String targetSrcName = toComponentName(dependencyInversion.getTargetSrc().getName());
TypeDeclaration srcClass = createClass(codebase, targetSrcName);
DependencyInversionWeaver.replaceDepedencyInClass(codebase, srcClass, dstType, interfaceType, interfaceName);
} else {
// Traverse all dependent classes(srcClass) in Codebase
for (CompilationUnit cu: codebase.getCompilationUnits()) {
TypeDeclaration srcClass = cu.types().getFirst();
if (srcClass.getTypeName().equals(dstName)) continue;
DependencyInversionWeaver.replaceDepedencyInClass(codebase, srcClass, dstType, interfaceType, interfaceName);
}
}
return codebase;
}
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;
}
}