All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hadii.stiff.diagram.StiffComponentPartitions Maven / Gradle / Ivy

The newest version!
package com.hadii.stiff.diagram;

import com.hadii.clarpse.sourcemodel.OOPSourceModelConstants;
import com.hadii.stiff.StiffCodeModel;
import com.hadii.stiff.extractor.ComponentRelation;
import com.hadii.stiff.extractor.ComponentRelations;
import edu.emory.mathcs.backport.java.util.Collections;
import org.gephi.graph.api.Column;
import org.gephi.graph.api.DirectedGraph;
import org.gephi.graph.api.GraphModel;
import org.gephi.graph.impl.GraphModelImpl;
import org.gephi.statistics.plugin.Modularity;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Represents partitions of a larger set of components to facilitate the creation of smaller Striff diagrams.
 */
public final class StiffComponentPartitions {

    private final Map componentNameMap = new HashMap<>();
    private final int softMaxSizeLimit;
    private final List> partitions = new ArrayList<>();
    private final Set keyComponents;
    private final Set keyRelations;
    private final ComponentRelations allRelations;
    private final int contextLevel;

    public StiffComponentPartitions(StiffCodeModel stiffModel, int softMaxSizeLimit, int contextLevel) {
        stiffModel.allComponents().forEach(diagramComponent -> {
            if (!diagramComponent.componentType().isBaseComponent()) {
                throw new IllegalArgumentException("Components that are being partitioned must be base components!");
            }
            this.componentNameMap.put(diagramComponent.uniqueName(), diagramComponent);
        });
        this.softMaxSizeLimit = softMaxSizeLimit;
        this.keyComponents = stiffModel.coreComponents();
        this.contextLevel = contextLevel;
        this.keyRelations = stiffModel.coreRelations();
        this.allRelations = new ComponentRelations(stiffModel.allRelations());
        generatePartitions(stiffModel.allComponents());
        // Final run to condense partitions in the overall partitions list
        mergeSmallerPartitions(this.partitions);
    }

    private void generatePartitions(Set unpartitionedComponents) {
        Modularity modularity = new Modularity();
        GraphModel graphModel = genGraphModel(unpartitionedComponents);
        modularity.setUseWeight(true);
        modularity.setResolution(1.);
        modularity.setRandom(false);
        // Runs partitioning algorithm and adds new partition label to every node in the graph
        modularity.execute(graphModel);
        Column column = graphModel.getNodeTable().getColumn(Modularity.MODULARITY_CLASS);
        // Map graph partition labels to components..
        Map> graphPartitionsMap = new HashMap<>();
        graphModel.getGraph().getNodes().forEach(node -> {
            int index = (int) node.getAttribute(column);
            if (!graphPartitionsMap.containsKey(index)) {
                graphPartitionsMap.put(index, new HashSet<>());
            }
            graphPartitionsMap.get(index).add(componentNameMap.get(node.getId()));
        });
        List> generatedPartitions = new ArrayList<>(graphPartitionsMap.values());
        // Filter partitions based on the requirement of having at least one key component in each partition
        filterUnImportantPartitions(generatedPartitions);
        // Condense smaller partitions together
        mergeSmallerPartitions(generatedPartitions);
        for (Set currPartition : generatedPartitions) {
            // For partitions that are too large and can be partitioned further, recursively partition them.
            if (currPartition.size() > this.softMaxSizeLimit * 1.5 && graphPartitionsMap.size() > 1) {
                generatePartitions(currPartition);
                // Otherwise, the partition looks good and can be added to our master list of partitions
            } else {
                this.partitions.add(currPartition);
            }
        }
    }

    private void mergeSmallerPartitions(List> partitions) {
        // For partitions that are too small, group them together with that partition they share the most relations with.
        for (int i = 0; i < partitions.size(); i++) {
            Set currPartition = partitions.get(i);
            for (int y = 0; y < partitions.size(); y++) {
                if (y != i) {
                    Set tmpPartition = partitions.get(y);
                    if (tmpPartition.size() + currPartition.size() < this.softMaxSizeLimit * 1.5) {
                        tmpPartition.addAll(currPartition);
                        partitions.remove(i);
                        i = -1;
                        break;
                    }
                }
            }
        }
    }

    private void filterUnImportantPartitions(List> partitions) {
        // Make sure partitions have at least one or more key components, and that the components in each partition are relevant
        // to the key components present in that partition.
        // Step 1: If a partition has no core components, it's not important, delete it.
        partitions.removeIf(partition -> Collections.disjoint(partition, this.keyComponents));
        // Step 2: Partitions should only consist of components that are related to the core components within that partition.
        for (Set partition : partitions) {
            Set partitionKeyComponents = partition.stream()
                    .distinct()
                    .filter(this.keyComponents::contains)
                    .collect(Collectors.toSet());
            // Key partition components consist of those components which are no more than contextLevel hops away from a key component.
            for (int i = 0; i < this.contextLevel; i++) {
                Set currPartitionKeyComponents = new HashSet<>(partitionKeyComponents);
                for (DiagramComponent cmp : currPartitionKeyComponents) {
                    if (this.allRelations.hasRelationsforComponent(cmp)) {
                        for (ComponentRelation componentRelation : this.allRelations.componentRelations(cmp)) {
                            partitionKeyComponents.add(componentRelation.targetComponent());
                        }
                    }
                }
            }
            partition.retainAll(partitionKeyComponents);
        }
    }

    private GraphModel genGraphModel(Set startingPartition) {
        GraphModel graphModel = new GraphModelImpl();
        // populate Graph object
        DirectedGraph directedGraph = graphModel.getDirectedGraph();
        startingPartition.forEach(diagramComponent -> directedGraph.addNode(graphModel.factory().newNode(diagramComponent.uniqueName())));
        startingPartition.forEach(diagramComponent -> {
            if (this.allRelations.hasRelationsforComponent(diagramComponent)) {
                this.allRelations.componentRelations(diagramComponent).forEach(componentRelation -> {
                    if (directedGraph.hasNode(componentRelation.targetComponent().uniqueName())) {
                        directedGraph.addEdge(graphModel.factory().newEdge(
                                String.valueOf(componentRelation.hashCode()),
                                directedGraph.getNode(componentRelation.originalComponent().uniqueName()),
                                directedGraph.getNode(componentRelation.targetComponent().uniqueName()),
                                0,
                                componentRelation.associationType().strength() * edgeWeightFactor(componentRelation),
                                true
                        ));
                    }
                });
            }
        });
        return graphModel;
    }

    public List> partitions() {
        return this.partitions;
    }

    /**
     * Boosts the default edge weight associated with the given relation based on certain factors.
     */
    private int edgeWeightFactor(ComponentRelation componentRelation) {
        int edgeWeightMultiplicationFactor = 1;
        if (this.keyComponents.contains(componentRelation.originalComponent())
                || componentRelation.originalComponent().packageName().equals(componentRelation.targetComponent().packageName())
                || componentRelation.originalComponent().componentType() == OOPSourceModelConstants.ComponentType.INTERFACE
                || componentRelation.originalComponent().modifiers().contains("abstract")) {
            edgeWeightMultiplicationFactor += 1;
        }
        if (this.keyComponents.contains(componentRelation.targetComponent())
                || this.keyRelations.contains(componentRelation)) {
            edgeWeightMultiplicationFactor += 2;
        }
        return edgeWeightMultiplicationFactor;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy