package generators;
import ast.*;
import designPatternExtensions.DependencyInversion;
import designPatternExtensions.InterfaceNode;
import models.dataConstraintModel.MapType;
import models.deltaAlgebra.ObjectNode;
public class DependencyInversionWeaver {
static Codebase weave(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 = 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
replaceDependencyInClass(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);
replaceDependencyInClass(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;
replaceDependencyInClass(codebase, srcClass, dstType, interfaceType, interfaceName);
}
}
return codebase;
}
static void replaceDependencyInClass(Codebase codebase, TypeDeclaration srcClass, models.algebra.Type dstType, models.algebra.Type interfaceType, String interfaceName) {
for (FieldDeclaration field: srcClass.getFields()) {
if (field.getType().equals(dstType)) {
// private Companies companies → private ICompanies companies
field.setType(interfaceType);
} else if (field.getType() instanceof MapType) {
MapType mapType = (MapType) field.getType();
// Map<String, Companies> → Map<String, ICompanies>
if (mapType.getValueType() != null && mapType.getValueType().getTypeName().equals(dstType.getTypeName())) {
field.setType(codebase.getMapType(mapType.getKeyType().getTypeName(), interfaceName));
}
}
}
for (MethodDeclaration method: srcClass.getMethods()) {
// Change constructor parameter types
if (method.isConstructor() && method.getParameters() != null) {
for (VariableDeclaration param: method.getParameters()) {
if (param.getType() != null && param.getType().equals(dstType)) {
// Customer(Companies companies) → Customer(ICompanies companies)
param.setType(interfaceType);
}
}
}
if (!method.isConstructor() && method.getParameters() != null) {
for (VariableDeclaration param: method.getParameters()) {
if (param.getType() != null && param.getType().equals(dstType)) {
param.setType(interfaceType);
}
}
}
// Change method return types
if (!method.isConstructor() && method.getReturnType() != null) {
if (method.getReturnType().getTypeName().equals(dstType.getTypeName())) {
// public Company getCompany() → public ICompany getCompany()
method.setReturnType(interfaceType);
}
}
}
}
public static InterfaceDeclaration createInterface(Codebase codebase, String name) {
if (codebase.getInterfaceCompilationUnit(name) != null) {
return codebase.getInterfaceCompilationUnit(name).interfaces().getFirst();
}
InterfaceDeclaration interfaceDeclaration = new InterfaceDeclaration(name);
CompilationUnit compilationUnit = new CompilationUnit(interfaceDeclaration);
codebase.addInterfaceCompilationUnit(name, compilationUnit);
return interfaceDeclaration;
}
//==================================================================================
// Same logic as ASTGenerator create methods
//==================================================================================
private static String toComponentName(String name) {
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
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;
}
}