package husacct.analyse.task.reconstruct.algorithms.hu.layers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeMap;
import org.apache.log4j.Logger;
import husacct.analyse.domain.IModelQueryService;
import husacct.analyse.task.reconstruct.AnalyseReconstructConstants;
import husacct.analyse.task.reconstruct.ReconstructArchitecture;
import husacct.analyse.task.reconstruct.algorithms.GraphOfSuClusters;
import husacct.analyse.task.reconstruct.algorithms.Algorithm_SuperClass;
import husacct.analyse.task.reconstruct.dto.ReconstructArchitectureDTO;
import husacct.analyse.task.reconstruct.parameters.ReconstructArchitectureParameterDTO;
import husacct.common.dto.ModuleDTO;
import husacct.common.dto.SoftwareUnitDTO;
import husacct.common.enums.ModuleTypes;
public class Layers_HUSACCT_Algorithm_SelectedModule extends Algorithm_SuperClass{
private ModuleDTO selectedModule;
private final Logger logger = Logger.getLogger(ReconstructArchitecture.class);
private ArrayList<SoftwareUnitDTO> softwareUnitsToIncludeInAlgorithm = new ArrayList<SoftwareUnitDTO>();
private HashMap<String, SoftwareUnitDTO> softwareUnitsToExclude = new HashMap<String, SoftwareUnitDTO>();
private GraphOfSuClusters graphOfSuClusters; // Each node in the graph represents 1-n SoftwareUnits. If *, it is a cohesive cluster of SUs.
private TreeMap<Integer, Set<Integer>> layersWithNodesMap = new TreeMap<Integer, Set<Integer>>();
private int backCallThreshold;
public Layers_HUSACCT_Algorithm_SelectedModule (IModelQueryService queryService) {
super(queryService);
graphOfSuClusters = new GraphOfSuClusters(this);
}
@Override
public void executeAlgorithm(ReconstructArchitectureDTO dto, IModelQueryService queryService) {
try {
backCallThreshold = dto.getThreshold();
selectedModule = dto.getSelectedModule();
// If the selectedModule is of type Facade or ExternalLibrary, nothing is done.
if ((selectedModule == null) || selectedModule.type.equals(ModuleTypes.EXTERNAL_LIBRARY.toString()) || selectedModule.type.equals(ModuleTypes.FACADE.toString())) {
return;
} else if (selectedModule.logicalPath.equals("")) {
selectedModule.logicalPath= "**"; // Root of intended software architecture
selectedModule.type = "Root"; // Root of intended software architecture
}
/* Test code
if (selectedModule.name.contains("validate")) {
boolean breakpoint = true;
} */
// Select the set of SUs to be used, and activate the component-identifying algorithm
if (selectedModule.logicalPath.equals("**")) {
SoftwareUnitDTO[] softwareUnitsInRoot = queryService.getSoftwareUnitsInRoot();
for (SoftwareUnitDTO rootUnit : softwareUnitsInRoot) {
if (!rootUnit.uniqueName.equals("xLibraries")) {
softwareUnitsToIncludeInAlgorithm.add(rootUnit);
}
}
if (softwareUnitsToIncludeInAlgorithm.size() == 1) {
SoftwareUnitDTO selectedSU = softwareUnitsToIncludeInAlgorithm.get(0);
softwareUnitsToIncludeInAlgorithm = getSetOfChildSoftwareUnits(selectedSU);
}
} else {
softwareUnitsToIncludeInAlgorithm = getRelevantSoftwareUnits();
}
graphOfSuClusters.initializeGraph(softwareUnitsToIncludeInAlgorithm, dto);
Set<Integer> allNodes = graphOfSuClusters.getNodes();
identifyLayers(allNodes);
if (layersWithNodesMap.size() >= 2) {
addIdentifiedLayersToIntendedArchitecture();
}
} catch (Exception e) {
logger.warn(" Exception: " + e );
}
}
private void identifyLayers(Set<Integer> allNodes) {
// 1) Assign all internalRootPackages to bottom layer
int layerId = 1;
layersWithNodesMap.put(layerId, allNodes);
// 2) Split the original bottom layer in a top layer and a (reduced) bottom layer, if appropriate.
identifyTopLayerBasedOnUnitsInBottomLayer(layerId);
// 3) Look iteratively for a new layer of nodes on top of the bottom layer, etc.
while (layersWithNodesMap.lastKey() > layerId) {
layerId++;
identifyTopLayerBasedOnUnitsInBottomLayer(layerId);
}
}
/**
* Logic: Move a software unit su1 from the current bottom layer to a new top layer
* if the software unit is using another software unit su2 in the current bottom layer.
* Complication: If su2 is using su1 as well and the backCallPercentage ((su2-->su1 / su1-->su2) x 100) is bigger than
* the callBackThreshold percentage, than su1 and su2 need to stay in the bottom layer (or both be moved to the top layer in case
* su1 and/or su2 is using another software unit su3 in the current bottom layer).
* @param bottomLayerId
*/
private void identifyTopLayerBasedOnUnitsInBottomLayer(int bottomLayerId) {
/* Test code
if (selectedModule.name.contains("graphics")) {
boolean breakpoint = true;
} */
HashSet<Integer> assignedNodesNewBottomLayer = new HashSet<Integer>();
HashSet<Integer> assignedNodesNewTopLayer = new HashSet<Integer>();
for (int fromNodeId : layersWithNodesMap.get(bottomLayerId)) {
boolean fromNodeUsesAnotherNode = false;
HashSet<Integer> clone = new HashSet<Integer>(layersWithNodesMap.get(bottomLayerId));
for (int toNodeId : clone) {
if (fromNodeId != toNodeId) {
int nrOfDependenciesFromTo = graphOfSuClusters.getNumberOfDependencies(fromNodeId, toNodeId);
int nrOfDependenciesToFrom = graphOfSuClusters.getNumberOfDependencies(toNodeId, fromNodeId);
if (nrOfDependenciesFromTo > 0) {
if (nrOfDependenciesFromTo > nrOfDependenciesToFrom) {
int backCallPercentage = ((nrOfDependenciesToFrom * 100) / nrOfDependenciesFromTo);
if (backCallPercentage <= backCallThreshold) {
fromNodeUsesAnotherNode = true;
} else {
// The fromNode and toNode are highly coupled, so leave them in the same layer.
}
}
}
}
}
if (fromNodeUsesAnotherNode) {
assignedNodesNewTopLayer.add(fromNodeId);
} else {
// Leave unit in the lower layer
assignedNodesNewBottomLayer.add(fromNodeId);
}
}
// Create a new top layer only if both new bottom layer and top layer contain at least one node.
if (!assignedNodesNewTopLayer.isEmpty() && !assignedNodesNewBottomLayer.isEmpty()) {
layersWithNodesMap.remove(bottomLayerId);
layersWithNodesMap.put(bottomLayerId, assignedNodesNewBottomLayer);
int newTopLayerId = bottomLayerId + 1;
layersWithNodesMap.put(newTopLayerId, assignedNodesNewTopLayer);
}
}
private void addIdentifiedLayersToIntendedArchitecture() {
int highestLevelLayer = layersWithNodesMap.size();
// Determine the hierarchicalLevels of the layers. The hierarchicalLevel of a layer within Define
// is as follows: the highest level layer has hierarchicalLevel = 1; the one below 2, etc.
int hierarchicalLevel = highestLevelLayer;
TreeMap<Integer, ArrayList<SoftwareUnitDTO>> layersWithSoftwareUnitsMap = new TreeMap<Integer, ArrayList<SoftwareUnitDTO>>();
for (int levelInName : layersWithNodesMap.keySet()) {
Set<Integer> nodesOfLayer = layersWithNodesMap.get(levelInName);
ArrayList<SoftwareUnitDTO> unitsOfLayer = new ArrayList<SoftwareUnitDTO>();
for (int nodeId : nodesOfLayer) {
unitsOfLayer.addAll(graphOfSuClusters.getSoftwareUnitsOfNode(nodeId));
}
layersWithSoftwareUnitsMap.put(hierarchicalLevel, unitsOfLayer);
hierarchicalLevel --;
}
// Determine the names of the layers. The layer-level in the layer name is reversed to the hierarchicalLevel within Define.
// The lowest level layer has levelInName = 1; the one above 2, etc.
int numberOfAddedLayers = 0;
int levelInName = highestLevelLayer;
for (int hierarchyLevel : layersWithSoftwareUnitsMap.keySet()) {
ArrayList<SoftwareUnitDTO> unitsOfLayer = layersWithSoftwareUnitsMap.get(hierarchyLevel);
String nameOfLayer = "Layer" + levelInName + determineLayerNameExtension(unitsOfLayer);
ModuleDTO newModule = createModule_andAddItToReverseList(nameOfLayer, selectedModule.logicalPath, "Layer", hierarchyLevel, unitsOfLayer);
if (!newModule.logicalPath.equals("")) {
numberOfAddedLayers ++;
levelInName --;
}
}
logger.info(" Number of added Layers: " + numberOfAddedLayers);
}
// If only one software unit is assigned, the name of this unit is returned.
// If several units are assigned, the name of the latgest unit is returned plus + "_etc".
private String determineLayerNameExtension(ArrayList<SoftwareUnitDTO> unitsOfLayer) {
String nameExtension = "";
if (unitsOfLayer.size() == 1) {
SoftwareUnitDTO unit = unitsOfLayer.get(0);
if (!unit.name.equals("")) {
if (unit.name.length() > 12) {
nameExtension = "_" + unit.name.substring(0, 11);
} else {
nameExtension = "_" + unit.name;
}
}
} else {
String nameLargestUnit = "";
int highestLinesOfCode = 0;
for (SoftwareUnitDTO unit : unitsOfLayer) {
int linesOfCode = queryService.getAnalysisStatistics(unit).selectionNrOfLinesOfCode;
if (linesOfCode > highestLinesOfCode) {
highestLinesOfCode = linesOfCode;
nameLargestUnit = unit.name;
}
}
if (!nameLargestUnit.equals("")) {
if (nameLargestUnit.length() > 12) {
nameLargestUnit = nameLargestUnit.substring(0, 11);
}
nameExtension = "_" + nameLargestUnit + "_etc";
}
}
return nameExtension;
}
// Returns the SUs assigned to selectedModule or, if only one SU is assigned, the children of this SU.
// In case the selectedModule is a Component, the SUs assigned to the interface should not be returned.
private ArrayList<SoftwareUnitDTO> getRelevantSoftwareUnits() {
ArrayList<SoftwareUnitDTO> softwareUnitsToReturn = new ArrayList<SoftwareUnitDTO>();
addSoftwareUnitsAssignedToComponentInterface_To_softwareUnitsToExcludeMap();
int numberOfAssignedSoftwareUnits = defineService.getAssignedSoftwareUnitsOfModule(selectedModule.logicalPath).size();
if (numberOfAssignedSoftwareUnits > 1) {
for(String logicalSoftwarePathSelectedModule : defineService.getAssignedSoftwareUnitsOfModule(selectedModule.logicalPath)){
SoftwareUnitDTO suDTO = queryService.getSoftwareUnitByUniqueName(logicalSoftwarePathSelectedModule);
if (!softwareUnitsToExclude.containsKey(suDTO.uniqueName)) {
softwareUnitsToReturn.add(suDTO);
}
}
} else if (numberOfAssignedSoftwareUnits == 1){
SoftwareUnitDTO assignedSU = new SoftwareUnitDTO("", "", "", "");
for(String uniqueNameAssignedSU : defineService.getAssignedSoftwareUnitsOfModule(selectedModule.logicalPath)){
assignedSU = queryService.getSoftwareUnitByUniqueName(uniqueNameAssignedSU);
}
for (SoftwareUnitDTO subModule : getSetOfChildSoftwareUnits(assignedSU)){
if (!softwareUnitsToExclude.containsKey(subModule.uniqueName)) {
softwareUnitsToReturn.add(subModule);
}
}
}
return softwareUnitsToReturn;
}
private void addSoftwareUnitsAssignedToComponentInterface_To_softwareUnitsToExcludeMap() {
if (selectedModule.type.equals(ModuleTypes.COMPONENT.toString())) {
for (ModuleDTO subModule : defineService.getModule_TheChildrenOfTheModule(selectedModule.logicalPath)) {
if (subModule.type.equals(ModuleTypes.FACADE.toString())) {
defineService.getAssignedSoftwareUnitsOfModule(subModule.logicalPath);
for (String assignedUnitUniqueName : defineService.getAssignedSoftwareUnitsOfModule(subModule.logicalPath)) {
SoftwareUnitDTO assignedUnit = queryService.getSoftwareUnitByUniqueName(assignedUnitUniqueName);
if (!assignedUnit.name.isEmpty()) {
softwareUnitsToExclude.put(assignedUnit.uniqueName, assignedUnit);
}
}
}
}
}
}
/** Returns the first set of children (number of children >= 2) in the decomposition hierarchy of the parent SoftwareUnit
* @param parentSoftwareUnit (of a SoftwareUnit)
* @return ArrayList<String> with unique names of children, or an empty list, if no child SoftwareUnits are existing.
*/
private ArrayList<SoftwareUnitDTO> getSetOfChildSoftwareUnits(SoftwareUnitDTO parentSoftwareUnit) {
ArrayList<SoftwareUnitDTO> childSoftwareUnits = new ArrayList<SoftwareUnitDTO>();
SoftwareUnitDTO softwareUnit = parentSoftwareUnit;
while (childSoftwareUnits.size() < 2) {
SoftwareUnitDTO[] childUnits = (queryService.getChildUnitsOfSoftwareUnit(softwareUnit.uniqueName));
if (childUnits.length == 0) {
if (!softwareUnit.equals(parentSoftwareUnit)) {
childSoftwareUnits.add(softwareUnit);
}
break;
} else if ((childUnits.length == 1)) {
softwareUnit = childUnits[0];
} else if ((childUnits.length >= 2)) {
for (SoftwareUnitDTO childUnit : childUnits) {
childSoftwareUnits.add(childUnit);
}
}
}
return childSoftwareUnits;
}
@Override
public ReconstructArchitectureDTO getAlgorithmParameterSettings() {
ReconstructArchitectureDTO reconstructArchitecture = new ReconstructArchitectureDTO();
reconstructArchitecture.approachId = AnalyseReconstructConstants.Algorithms.Layers_HUSACCT_SelectedModule;
reconstructArchitecture.threshold = 10;
reconstructArchitecture.relationType = AnalyseReconstructConstants.RelationTypes.allDependencies;
reconstructArchitecture.granularity = AnalyseReconstructConstants.Granularities.PackagesAndClasses;
reconstructArchitecture.parameterDTOs = createParameterPanels();
return reconstructArchitecture;
}
private ArrayList<ReconstructArchitectureParameterDTO> createParameterPanels(){
ArrayList<ReconstructArchitectureParameterDTO> parameterDTOs = new ArrayList<>();
parameterDTOs.add(ReconstructArchitectureParameterDTO.DefaultParameterDTOs.createThresholdParameter(10));
parameterDTOs.add(ReconstructArchitectureParameterDTO.DefaultParameterDTOs.createRelationTypeParameter(AnalyseReconstructConstants.RelationTypes.allDependencies));
parameterDTOs.add(ReconstructArchitectureParameterDTO.DefaultParameterDTOs.createGranularityPanel(AnalyseReconstructConstants.Granularities.PackagesAndClasses));
return parameterDTOs;
}
}