import re
import sys
def remove_comments_and_strings(code):
"""
コメント(ブロックコメント、行コメント)や文字列リテラルを除去し、
制御構造キーワードの誤検出を防ぎます。
"""
# ブロックコメントの除去 (DOTALLオプションで改行も含む)
code = re.sub(r'/\*.*?\*/', '', code, flags=re.DOTALL)
# 行コメントの除去
code = re.sub(r'//.*', '', code)
# 文字列リテラルの除去(単純に "" に置換)
code = re.sub(r'"(?:\\.|[^"\\])*"', '""', code)
return code
def extract_methods(code):
"""
シンプルな正規表現と括弧のカウントにより、
ソース内の各メソッドブロックを抽出します。
"""
method_pattern = re.compile(
r'(public|protected|private|static|\s)+[\w\<\>\[\]]+\s+\w+\s*\([^)]*\)\s*\{'
)
methods = []
for match in method_pattern.finditer(code):
start_index = match.end() - 1 # '{' の位置
brace_count = 1
end_index = start_index + 1
while end_index < len(code) and brace_count > 0:
if code[end_index] == '{':
brace_count += 1
elif code[end_index] == '}':
brace_count -= 1
end_index += 1
method_body = code[match.start():end_index]
methods.append(method_body)
return methods
def count_decision_points(method_code):
"""
以下のキーワードに着目して決定点をカウントします。
- if
- for
- while
- case
- catch
- 三項演算子 '?'
- 論理演算子 '&&' および '||'
"""
decisions = 0
decisions += len(re.findall(r'\bif\s*\(', method_code))
decisions += len(re.findall(r'\bfor\s*\(', method_code))
decisions += len(re.findall(r'\bwhile\s*\(', method_code))
decisions += len(re.findall(r'\bcase\b', method_code))
decisions += len(re.findall(r'\bcatch\s*\(', method_code))
decisions += len(re.findall(r'\?', method_code))
decisions += len(re.findall(r'&&', method_code))
decisions += len(re.findall(r'\|\|', method_code))
return decisions
def extract_relevant_lines(method_code):
"""
巨大なメソッド全体ではなく、
テストケース作成のために必要な箇所(メソッドシグネチャおよび決定点を含む行)だけを抽出して返します。
"""
lines = method_code.splitlines()
relevant_lines = []
decision_keywords = ['if(', 'if (', 'for(', 'for (', 'while(', 'while (',
'case ', 'catch(', 'catch (', '?', '&&', '||']
signature_included = False
for line in lines:
stripped = line.strip()
if not signature_included and stripped:
relevant_lines.append(stripped)
signature_included = True
continue
for kw in decision_keywords:
if kw in stripped:
relevant_lines.append(stripped)
break
return "\n".join(relevant_lines)
def calculate_cyclomatic_complexity(java_filename):
"""
指定したJavaファイルからサイクロマティック複雑度を算出し、
以下の項目を出力します。
・E: グラフのエッジ数
・N: グラフのノード数
・P: 連結成分数(ここではメソッド数)
・M: サイクロマティック複雑度
また、算出対象となったロジックの必要な箇所(決定点を含む部分)と、
各メソッドごとのE, N, P, 複雑度も出力します。
"""
with open(java_filename, 'r', encoding='utf-8') as f:
code = f.read()
code = remove_comments_and_strings(code)
methods = extract_methods(code)
if not methods:
methods = [code]
global_decisions = 0
print("【各メソッドごとの算出対象ロジックおよび統計】")
for idx, method in enumerate(methods, start=1):
d = count_decision_points(method)
global_decisions += d
# 各メソッドの統計計算
method_P = 1 # 各メソッドは1つの連結成分
method_M = d + 1 # 複雑度 = d + 1
method_N = d + 2 # ノード数 = d + 2
method_E = 2 * d + 1 # エッジ数 = 2*d + 1
relevant = extract_relevant_lines(method)
print(f"\n---- メソッド {idx} ----")
print("【対象ロジック】")
print(relevant)
print("【統計】")
print(" E (エッジ数):", method_E)
print(" N (ノード数):", method_N)
print(" P (連結成分数):", method_P)
print(" サイクロマティック複雑度:", method_M)
# グローバル統計(全メソッド合算)
P = len(methods)
global_M = global_decisions + P
global_N = global_decisions + 2 * P
global_E = 2 * global_decisions + P
print("\n【全体のサイクロマティック複雑度の結果】")
print("E (グラフのエッジ数):", global_E)
print("N (グラフのノード数):", global_N)
print("P (連結成分数/メソッド数):", P)
print("サイクロマティック複雑度:", global_M)
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python cyclomatic.py <JavaFile>")
sys.exit(1)
java_filename = sys.argv[1]
calculate_cyclomatic_complexity(java_filename)
カテゴリー