package husacct.analyse.task.reconstruct.algorithms;
import husacct.analyse.task.reconstruct.AnalyseReconstructConstants;
import husacct.analyse.task.reconstruct.dto.ReconstructArchitectureDTO;
import husacct.common.dto.SoftwareUnitDTO;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import org.apache.log4j.Logger;
public class GraphOfSuClusters {
HashMap<Integer, HashSet<SoftwareUnitDTO>> nodes; // Key = nodeId, Value = SU assigned to cohesive cluster of SU's.
HashMap<String, Integer> edges; // Key = fromNodeId1 + "." + toNodeId2, Value = number of dependencies from node1 to node2.
Algorithm_SuperClass iAlgoritm;
private int backCallThreshold;
private String typesOfDependencies;
private String granularity;
private String selectedModule;
private final Logger logger = Logger.getLogger(GraphOfSuClusters.class);
public GraphOfSuClusters(Algorithm_SuperClass iAlgoritm) {
nodes = new HashMap<Integer, HashSet<SoftwareUnitDTO>>();
edges = new HashMap<String, Integer>();
this.iAlgoritm = iAlgoritm;
}
public void initializeGraph(ArrayList<SoftwareUnitDTO> softwareUnitsToInclude, ReconstructArchitectureDTO dto) {
try {
backCallThreshold = dto.getThreshold();
typesOfDependencies = dto.getRelationType();
granularity = dto.granularity;
selectedModule = dto.getSelectedModule().name;
createNodesInClusteredGraph(softwareUnitsToInclude);
setEdges(typesOfDependencies);
} catch (Exception e) {
logger.warn(" Exception: " + e );
}
}
// The returned set is backed by the map, so changes to the map are reflected in the set, and vice-versa. Beware of concurrent modifications.
public Set<Integer> getNodes() {
return nodes.keySet();
}
// Returns null if this map contains no mapping for uniqueName.
public HashSet<SoftwareUnitDTO> getSoftwareUnitsOfNode(int nodeId) {
return nodes.get(nodeId);
}
public int getNumberOfDependencies(int fromNodeId, int toNodeId) {
int returnValue = 0;
String searchKey = fromNodeId + "." + toNodeId;
if (edges.containsKey(searchKey)) {
returnValue = edges.get(searchKey);
}
return returnValue;
}
/**
* Creates a graph of clustered SotwareUnits. Each node is a software unit, or a cluster of units that are highly coupled.
* Individual types may be clustered into one node, based on the granularity setting.
* A SoftwareUnit without cyclic dependencies (or a cyclic dependency below the backCallThreshold) gets its own node.
* If SoftwareUnit su1 is using su2 and su2 is using su1 as well and the backCallThreshold ((su2-->su1 / su1-->su2) x 100) is bigger than
* the callBackThreshold percentage, than su1 and su2 are clustered into one node, since these software units need to stay in the same layer.
* If su3 is also tightly coupled with su1 or su2, it is added to this cluster as well.
*/
private void createNodesInClusteredGraph(ArrayList<SoftwareUnitDTO> softwareUnitsToInclude) {
int nodeIdofNodeWithClassesInRoot = 1;
int highestNodeId = 1;
ArrayList<SoftwareUnitDTO> softwareUnitsToIncludeClone = new ArrayList<SoftwareUnitDTO> (softwareUnitsToInclude);
// 1) If fromSoftwareUnit is not of type "package", while the granularity is set to packages, than add this SU to NodeWithClassesInRoot
for (SoftwareUnitDTO fromSoftwareUnit : softwareUnitsToInclude) {
// If fromSoftwareUnit is of type "class", while the granularity setting excludes classes, than add this SU to NodeWithClassesInRoot
if (!fromSoftwareUnit.type.toLowerCase().equals("package")) {
if (granularity.equals(AnalyseReconstructConstants.Granularities.Packages)) {
if (!nodes.containsKey(nodeIdofNodeWithClassesInRoot)) {
addNode(nodeIdofNodeWithClassesInRoot);
}
addSoftwareUnitToNode(nodeIdofNodeWithClassesInRoot, fromSoftwareUnit);
}
}
}
// 2) Add all software units to a node and cluster units with direct cyclic dependencies above backCallThreshold into one node.
for (SoftwareUnitDTO fromSoftwareUnit : softwareUnitsToInclude) {
/* Test code
if (fromSoftwareUnit.name.contains("productdispatcher")) {
boolean breakpoint = true;
} */
for (SoftwareUnitDTO toSoftwareUnit : softwareUnitsToIncludeClone) {
if (!toSoftwareUnit.uniqueName.equals(fromSoftwareUnit.uniqueName)) {
int nrOfDependenciesFromTo = iAlgoritm.getRelationsBetweenSoftwareUnits(fromSoftwareUnit.uniqueName, toSoftwareUnit.uniqueName, typesOfDependencies).size();
int nrOfDependenciesToFrom = iAlgoritm.getRelationsBetweenSoftwareUnits(toSoftwareUnit.uniqueName, fromSoftwareUnit.uniqueName, typesOfDependencies).size();
if (nrOfDependenciesFromTo > 0) {
if (nrOfDependenciesToFrom > 0) {
if (nrOfDependenciesFromTo >= nrOfDependenciesToFrom) { // Prevents erroneously added highly coupled units.
int backCallPercentage = ((nrOfDependenciesToFrom * 100) / nrOfDependenciesFromTo);
if (backCallPercentage > backCallThreshold) {
// SoftwareUnit and otherSoftwareUnit are highly coupled.
// Per from and to: Find node that contains the unit
int nodeIdFrom = findNodeThatContainsSoftwareUnit(fromSoftwareUnit);
int nodeIdTo = findNodeThatContainsSoftwareUnit(toSoftwareUnit);
if ((nodeIdFrom == 0) && (nodeIdTo == 0)) {
// If no node is found: a) raise highestNodeId; b) create new node with both units.
highestNodeId ++;
addNode(highestNodeId);
addSoftwareUnitToNode(highestNodeId, fromSoftwareUnit);
addSoftwareUnitToNode(highestNodeId, toSoftwareUnit);
} else if ((nodeIdFrom != 0) && (nodeIdTo == 0)) {
// If one node is found, add units to node.
addSoftwareUnitToNode(nodeIdFrom, toSoftwareUnit);
} else if ((nodeIdFrom == 0) && (nodeIdTo != 0)){
addSoftwareUnitToNode(nodeIdTo, fromSoftwareUnit);
} else if ((nodeIdFrom != 0) && (nodeIdTo != 0) && (nodeIdFrom != nodeIdTo)) {
// If both nodes are found, add the units of one node to the other node, and remove the empty node.
for (SoftwareUnitDTO su : getSoftwareUnitsOfNode(nodeIdTo)) {
addSoftwareUnitToNode(nodeIdFrom, su);
}
nodes.remove(nodeIdTo);
}
}
}
}
}
}
}
}
// 3) Add non-coupled units as nodes to graph
for (SoftwareUnitDTO softwareUnit : softwareUnitsToIncludeClone) {
int clusterId = findNodeThatContainsSoftwareUnit(softwareUnit);
if (clusterId == 0) {
highestNodeId ++;
addNode(highestNodeId);
addSoftwareUnitToNode(highestNodeId, softwareUnit);
}
}
// 4) Iteratively, merge nodes with direct cyclic dependencies above backCallThreshold.
try {
HashMap<Integer, Integer> nodesToMerge = new HashMap<Integer, Integer>();
boolean repeatClusteringOverAllNodes = true;
while (repeatClusteringOverAllNodes) {
setEdges(typesOfDependencies);
repeatClusteringOverAllNodes = false;
for (int fromNodeId : getNodes()) {
for (int toNodeId : getNodes()) {
if (fromNodeId != toNodeId) {
int nrOfDependenciesFromTo = getNumberOfDependencies(fromNodeId, toNodeId);
int nrOfDependenciesToFrom = getNumberOfDependencies(toNodeId, fromNodeId);
if (nrOfDependenciesFromTo > 0) {
if (nrOfDependenciesToFrom > 0) {
if (nrOfDependenciesFromTo >= nrOfDependenciesToFrom) { // Prevents erroneously added highly coupled units.
int backCallPercentage = ((nrOfDependenciesToFrom * 100) / nrOfDependenciesFromTo);
if (backCallPercentage > backCallThreshold) {
// The two nodes are highly coupled, so merge them and repeat the procedure
nodesToMerge.put(fromNodeId, toNodeId);
repeatClusteringOverAllNodes = true;
logger.info(" Merging: " + selectedModule + " " + fromNodeId + " " + toNodeId );
}
}
}
}
}
}
for (int fromNode : nodesToMerge.keySet()) {
int toNode = nodesToMerge.get(fromNode);
if (nodes.containsKey(toNode)) {
for (SoftwareUnitDTO su : getSoftwareUnitsOfNode(toNode)) {
addSoftwareUnitToNode(fromNode, su);
}
nodes.remove(toNode);
}
}
if (nodesToMerge.size() > 0) {
break;
}
}
}
} catch (Exception e) {
logger.warn(" Exception: " + e );
//e.printStackTrace();
}
}
private void setEdges(String typesOfDependencies) {
for (int fromNode : getNodes()) {
for (int toNode : getNodes()) {
int totalNumber = 0;
for (SoftwareUnitDTO fromSoftwareUnit : nodes.get(fromNode)) {
for (SoftwareUnitDTO toSoftwareUnit : nodes.get(toNode)) {
if (!toSoftwareUnit.uniqueName.equals(fromSoftwareUnit.uniqueName)) {
int nrOfDependenciesFromTo = iAlgoritm.getRelationsBetweenSoftwareUnits(fromSoftwareUnit.uniqueName, toSoftwareUnit.uniqueName, typesOfDependencies).size();
totalNumber = totalNumber + nrOfDependenciesFromTo;
}
}
}
String searchKey = fromNode + "." + toNode;
edges.put(searchKey, totalNumber);
}
}
}
// Returns nodeId if node that contains softwareUnit, or 0 if it is not contained in a node.
private int findNodeThatContainsSoftwareUnit(SoftwareUnitDTO softareUnit) {
int returnValue = 0;
for (int nodeId : getNodes()) {
for (SoftwareUnitDTO unitOfNode : getSoftwareUnitsOfNode(nodeId)) {
if (unitOfNode.uniqueName.equals(softareUnit.uniqueName)) {
return nodeId;
}
}
}
return returnValue;
}
private void addNode(int nodeId) {
HashSet<SoftwareUnitDTO> softwareUnits = new HashSet<SoftwareUnitDTO>();
nodes.put(nodeId, softwareUnits);
}
private void addSoftwareUnitToNode(int nodeId, SoftwareUnitDTO softwareUnit) {
if (nodes.containsKey(nodeId)) {
HashSet<SoftwareUnitDTO> softwareUnits = nodes.get(nodeId);
softwareUnits.add(softwareUnit);
nodes.put(nodeId, softwareUnits);
}
}
}