Newer
Older
DesignCraft / src / main / java / generators / DependencyInversionWeaver.java
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;
    }

}