Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 113 additions & 39 deletions codart/metrics/complexity.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import os
import time
import traceback

from design_4_testability.class_diagram_extraction.class_diagram import ClassDiagram
from design_4_testability.config import test_class_diagram
from design_4_testability import config
from design_4_testability.config2 import projects_info

import networkx as nx
import csv
Expand All @@ -18,35 +22,58 @@ def __init__(self, class_diagram):
for node in candidate_nodes:
self.inheritance_complexity_dic[node] = self.__calculate_inheritance_complexity(node)

# NEW FUNCTION — Fast detection whether a use_def relation exists
def has_use_def_relation(self, source, target):
"""
Quickly returns True if there exists ANY path between source → target
that includes at least one use_def edge.
"""

# Check each use_def edge and see if it can participate in a path
for u, v, data in self.CDG.edges(data=True):
if data.get("relation_type") == "use_def":
# Is source -> u reachable AND v -> target reachable?
if nx.has_path(self.class_diagram, source, u) and nx.has_path(self.class_diagram, v, target):
return True
return False

def calculate_interaction_complexity(self, source, target):

# Case 1: No path at all → None ---
if not nx.has_path(self.class_diagram, source, target):
return None

# Case 2: Path exists, but no use_def on any path → complexity = 1 ---
if not self.has_use_def_relation(source, target):
return 1

# Case 3: There is use_def on at least one path → compute formula ---
complexity = 1
has_path = False
for path in nx.all_simple_paths(self.class_diagram, source=source, target=target):
has_path = True
complexity *= self.__calculate_path_complexity(path)
if not has_path:
complexity = None

return complexity

def __calculate_path_complexity(self, path):
complexity = 1
for i in range(len(path) - 1):
if self.CDG[path[i]][path[i+1]]['relation_type'] == 'use_def':
if path[i] in self.inheritance_complexity_dic:
complexity *= self.inheritance_complexity_dic[path[i]]
u, v = path[i], path[i + 1]
if self.CDG.has_edge(u, v) and self.CDG[u][v].get('relation_type') == 'use_def':
if u in self.inheritance_complexity_dic:
complexity *= self.inheritance_complexity_dic[u]
return complexity

def __calculate_inheritance_complexity(self, node):
complexity = 0
stack = []
stack.append(node)
stack = [node]
# stack.append(node)
depth_dic = {node: 1}

depth_dic = {node:1}
while stack != []:
while stack:
current_node = stack.pop()
is_leave = True
for neighbor in self.CDG[current_node]:
if (current_node in self.CDG[neighbor]):
if current_node in self.CDG[neighbor]:
if self.CDG[current_node][neighbor]['relation_type'] == 'child' and self.CDG[neighbor][current_node]['relation_type'] == 'parent':
is_leave = False
stack.append(neighbor)
Expand All @@ -58,23 +85,25 @@ def __calculate_inheritance_complexity(self, node):

def __find_inheritance_candidates(self):
candidates = set()
for edge in self.CDG.edges:
if self.CDG.edges[edge]['relation_type'] == 'parent':
candidates.add(edge[1])
for u, v, d in self.CDG.edges(data=True):
if d.get('relation_type') == 'parent':
candidates.add(v)
return candidates

def get_matrix(self):
start_time = time.time()
node_list = list(self.CDG.nodes)
node_list = sorted(self.CDG.nodes)
no_nodes = len(node_list)
node_list.sort()

matrix = []
for s in range(no_nodes):
matrix.append([])
for d in range(no_nodes):
if self.CDG.nodes[node_list[s]]['type'] == "class" and self.CDG.nodes[node_list[d]]['type'] == "class":
complexity = self.calculate_interaction_complexity(node_list[s], node_list[d])
src = node_list[s]
dst = node_list[d]

if self.CDG.nodes[src]['type'] == "class" and self.CDG.nodes[dst]['type'] == "class":
complexity = self.calculate_interaction_complexity(src, dst)
matrix[s].append(complexity)
else:
matrix[s].append(None)
Expand All @@ -90,16 +119,11 @@ def get_avg_of_matrix(matrix):
if j is not None:
n += 1
s += j
return s / n
return s / n if n > 0 else 0

@staticmethod
def get_sum_of_matrix(matrix):
s = 0
for i in matrix:
for j in i:
if j is not None:
s += j
return s
return sum(val for row in matrix for val in row if val is not None)

def save_csv(self, path):
node_list = list(self.CDG.nodes)
Expand All @@ -109,25 +133,75 @@ def save_csv(self, path):

with open(path, 'w', encoding='UTF8') as f:
writer = csv.writer(f)
writer.writerow(header)

# write the header
no_row = 0
writer.writerow(header)
for s in range(no_nodes):
for d in range(no_nodes):
print(s, d)
if self.CDG.nodes[node_list[s]]['type'] == "class" and self.CDG.nodes[node_list[d]][
'type'] == "class":
complexity = self.calculate_interaction_complexity(str(node_list[s]), str(node_list[d]))
no_row += 1

if __name__ == "__main__":
cd = ClassDiagram(java_project_address='', base_dirs='', files=[], index_dic={})
cd.class_diagram_graph = test_class_diagram
cd.show(cd.class_diagram_graph)
c = Complexity(cd)
print(c.calculate_interaction_complexity(8, 7))
src = node_list[s]
dst = node_list[d]
if self.CDG.nodes[src]['type'] == "class" and self.CDG.nodes[dst]['type'] == "class":
complexity = self.calculate_interaction_complexity(src, dst)
writer.writerow([src, dst, complexity])


if __name__ == "__main__":
output_csv = "results_complexity.csv"

if not os.path.exists(output_csv):
with open(output_csv, mode='w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(["Project Name", "Address", "Number of Nodes", "Sum of Complexities", "Average Complexity"])

for java_project in projects_info:
java_project_address = projects_info[java_project]['path']
base_dirs = projects_info[java_project]['base_dirs']

print(f'\n=== Running project: {java_project} ===')
# print('Project address:', java_project_address)
# print('Base dirs:', base_dirs)

try:
cd = ClassDiagram(java_project_address=java_project_address,
base_dirs=base_dirs,
files=[],
index_dic={})
cd.make_class_diagram()

c = Complexity(cd)
node_list = list(cd.class_diagram_graph.nodes)
node_count = len(node_list)

total_complexity = 0

for source in node_list:
for target in node_list:
if source != target:
complexity = c.calculate_interaction_complexity(source, target)
if complexity is not None:
total_complexity += complexity
print(f"Interaction Complexity between {source} and {target}: {complexity}")
else:
print(f"Warning: None value between {source} and {target}")

average_complexity = total_complexity / node_count if node_count > 0 else 0

print(f"\n Project: {java_project}")
print(f"Nodes: {node_count}")
print(f"Total Complexity: {total_complexity}")
print(f"Average Complexity: {average_complexity:.2f}")

with open(output_csv, mode='a', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow([
java_project,
java_project_address,
node_count,
total_complexity,
round(average_complexity, 2)
])
except Exception as e:
print(f"Error processing project {java_project}: {e}")
traceback.print_exc()

67 changes: 34 additions & 33 deletions codart/metrics/design_testability_prediction2.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,27 @@
__author__ = 'Morteza Zakeri'

import os
import sys
import gc

import pandas as pd
import joblib
from joblib import Parallel, delayed
import time

os.add_dll_directory("C:\\Program Files\\SciTools\\bin\\pc-win64\\")
sys.path.append("C:\\Program Files\\SciTools\\bin\\pc-win64\\python")
import understand as und

from codart.metrics import metrics_names
from codart.metrics.metrics_coverability import UnderstandUtility

scaler1 = joblib.load(
os.path.join(os.path.dirname(__file__),
'../test_effectiveness/sklearn_models_nodes_regress/DS07710_scaler.joblib')
'C:/Users/98910/Desktop/dataset/DS07710_scaler.joblib')
)
model5 = joblib.load(
os.path.join(os.path.dirname(__file__),
'../test_effectiveness/sklearn_models_nodes_regress/VR1_DS7.joblib')
'C:/Users/98910/Desktop/dataset/VoR1_DS2.joblib')
)


Expand Down Expand Up @@ -274,10 +278,11 @@ def compute_java_class_metrics2(cls, db=None, entity=None):
return class_metrics


def do(class_entity_long_name, project_db_path):
import understand as und
db = und.open(project_db_path)
def do(class_entity_long_name, db):
class_entity = UnderstandUtility.get_class_entity_by_name(class_name=class_entity_long_name, db=db)
if class_entity is None:
return None

one_class_metrics_value = [class_entity.longname()]

# print('Calculating package metrics')
Expand All @@ -304,8 +309,6 @@ def do(class_entity_long_name, project_db_path):
one_class_metrics_value.extend([package_metrics_dict[metric_name] for
metric_name in TestabilityMetrics.get_package_metrics_names()])

db.close()
del db
# print(one_class_metrics_value)
# quit()
return one_class_metrics_value
Expand All @@ -329,29 +332,27 @@ def compute_metrics_by_class_list(cls, project_db_path, n_jobs):
# class_entities = cls.read_project_classes(db=db, classes_names_list=class_list, )
# print(project_db_path)
db = und.open(project_db_path)
class_list = UnderstandUtility.get_project_classes_longnames_java(db=db)
db.close()
# del db

if n_jobs == 0: # Sequential computing
res = [do(class_entity_long_name, project_db_path) for class_entity_long_name in class_list]
else: # Parallel computing
res = Parallel(n_jobs=n_jobs, )(
delayed(do)(class_entity_long_name, project_db_path) for class_entity_long_name in class_list
)
res = list(filter(None, res))

columns = ['Class']
columns.extend(TestabilityMetrics.get_all_primary_metrics_names())
# print('*' * 50)
# print(len(columns), columns)
# print('*' * 50)

df = pd.DataFrame(data=res, columns=columns)
# print('df for class {0} with shape {1}'.format(project_name, df.shape))
# df.to_csv(csv_path + project_name + '.csv', index=False)
# print(df)
return df
try:
class_list = UnderstandUtility.get_project_classes_longnames_java(db=db)

results = []
for i, class_name in enumerate(class_list):
if i % 100 == 0:
print(f"[INFO] Processed {i}/{len(class_list)} classes...")
gc.collect() # <-- CRITICAL

res = do(class_name, db)
if res is not None:
results.append(res)

columns = ['Class']
columns.extend(TestabilityMetrics.get_all_primary_metrics_names())
df = pd.DataFrame(data=results, columns=columns)
return df

finally:
db.close()
gc.collect()


class TestabilityModel:
Expand Down Expand Up @@ -409,8 +410,8 @@ def main(project_db_path, initial_value=1.0, verbose=False, log_path=None):

# Test module
if __name__ == '__main__':
db_path_ = r'E:/LSSDS/CodART/Experimental1/udbs/jvlt-1.3.2.und' # This path should be replaced for each project
project_name_ = 'jvlt-1.3.2'
db_path_ = r'F:\benchmarks-proposal\java-corpus\bcel-5.2\bcel-5.2.und' # This path should be replaced for each project
project_name_ = 'bcel-5.2'

log_path_ = os.path.join(os.path.dirname(__file__), project_name_ + '_testability_s2.csv')

Expand Down
Loading