0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Java呼び出し元解析ツール(Python + config.yaml 対応)

Last updated at Posted at 2025-05-01

Java呼び出し元解析ツール(Python + config.yaml 対応)

特定のメソッドを呼び出しているJavaファイルを再帰的に解析し、最終的にどのプロジェクトで利用されているかをCSV形式で出力するツールです。

特徴

  • common を起点に呼び出しをたどる
  • 呼び出し元のファイル・クラス・メソッド名を出力
  • 最終呼び出し元が common を抜けた時点で解析終了
  • 設定は config.yaml に集約

想定ディレクトリ構成

C:
└─ python
└─ 依存関係調査
├─ analyze_calls.py
└─ config.yaml

実行方法


import os
import re
import csv
from pathlib import Path
from collections import defaultdict, deque

# === 設定 ===
PROJECT_ROOT = Path("C:/workspace_abc/6末")
COMMON_PROJECT = "ava-common"
TARGET_METHOD = "selectByMemberId"
TARGET_DAOS = {"aaaDao", "aaaBaseDao"}
EXCLUDE_PROJECTS = {"temp-project", "legacy"}
OUTPUT_FILE = "calls_cross_class.csv"
# =============

def get_all_java_files():
    return list(PROJECT_ROOT.rglob("*.java"))

def extract_project_name(path: Path):
    for part in path.parts:
        if (PROJECT_ROOT / part).exists():
            return part
    return "unknown"

def extract_class_and_methods(file_path):
    class_name = file_path.stem
    methods = {}
    var_types = {}
    try:
        with open(file_path, encoding="utf-8", errors="ignore") as f:
            lines = f.readlines()
            current_method = None
            brace_count = 0
            for i, line in enumerate(lines):
                # クラス名取得
                class_match = re.search(r'\b(class|interface|enum)\s+(\w+)', line)
                if class_match:
                    class_name = class_match.group(2)

                # フィールド・ローカル変数からクラス名と変数名を取得
                var_match = re.findall(r'\b(\w+)\s+(\w+)\s*(=|;)', line)
                for vtype, vname, _ in var_match:
                    if vtype != "return" and len(vname) < 50:
                        var_types[vname] = vtype

                # メソッド定義取得
                def_match = re.match(r'\s*(public|private|protected)?\s+\w[\w<>\[\]]*\s+(\w+)\s*\([^;]*\)\s*\{?', line)
                if def_match:
                    current_method = def_match.group(2)
                    methods[current_method] = {
                        "start": i,
                        "calls": [],
                        "file": file_path,
                        "class": class_name,
                        "line_text": [],
                        "vars": var_types.copy()
                    }
                    brace_count = line.count("{") - line.count("}")
                    continue
                if current_method:
                    brace_count += line.count("{") - line.count("}")
                    if brace_count < 0:
                        current_method = None
                        continue
                    methods[current_method]["line_text"].append(line.strip())
                    call_match = re.findall(r'(\w+)\.(\w+)\s*\(', line)
                    if call_match:
                        methods[current_method]["calls"].extend(call_match)
    except Exception:
        pass
    return class_name, methods

def build_class_method_map(java_files):
    class_to_file = {}
    all_methods = {}
    for file in java_files:
        class_name, methods = extract_class_and_methods(file)
        class_to_file[class_name] = file
        for method_name, meta in methods.items():
            all_methods[(class_name, method_name)] = meta
    return class_to_file, all_methods

def method_calls_target_dao(method_info):
    joined = " ".join(method_info["line_text"])
    for dao in TARGET_DAOS:
        pattern = rf'\b(?:this\.)?{re.escape(dao)}\.{re.escape(TARGET_METHOD)}\s*\('
        if re.search(pattern, joined):
            return True
    return False

def find_entry_methods(all_methods):
    return [(cls, mname) for (cls, mname), meta in all_methods.items() if method_calls_target_dao(meta)]

def build_call_chains(entries, all_methods):
    chains = []
    for cls, mname in entries:
        visited = set()
        queue = deque()
        queue.append([(cls, mname)])
        while queue:
            path = queue.popleft()
            last_cls, last_mth = path[-1]
            visited.add((last_cls, last_mth))
            for (cand_cls, cand_mth), meta in all_methods.items():
                for var, called in meta["calls"]:
                    called_cls = meta["vars"].get(var)
                    if called_cls == last_cls and called == last_mth:
                        if (cand_cls, cand_mth) in visited:
                            continue
                        new_path = path + [(cand_cls, cand_mth)]
                        project = extract_project_name(meta["file"])
                        if COMMON_PROJECT not in str(meta["file"]) and project not in EXCLUDE_PROJECTS:
                            chains.append(new_path)
                        else:
                            queue.append(new_path)
    return chains, all_methods

def safe_str(s):
    return str(s).encode("utf-8", errors="replace").decode("utf-8")

def write_chains_to_csv(chains, method_map):
    max_depth = max(len(c) for c in chains) if chains else 0
    headers = []
    for i in range(max_depth):
        headers += [f"呼び出し元{i+1}_ファイル", f"呼び出し元{i+1}_クラス", f"呼び出し元{i+1}_メソッド"]
    headers += ["最終呼び出し元ファイル", "プロジェクト名", "呼び出し種別"]

    with open(OUTPUT_FILE, "w", newline="", encoding="utf-8-sig") as f:
        writer = csv.writer(f)
        writer.writerow(headers)
        for chain in chains:
            row = []
            for cls, mth in chain:
                meta = method_map[(cls, mth)]
                row += [safe_str(meta["file"].name), safe_str(meta["class"]), safe_str(mth)]
            pad = (max_depth - len(chain)) * ["", "", ""]
            final_file = method_map[chain[-1]].get("file", "")
            project = extract_project_name(final_file)
            call_type = "直接" if COMMON_PROJECT not in str(method_map[chain[0]].get("file", "")) else "common経由"
            row += [safe_str(final_file.name), safe_str(project), call_type]
            writer.writerow(row)

# 実行
java_files = get_all_java_files()
class_map, method_map = build_class_method_map(java_files)
entries = find_entry_methods(method_map)
chains, full_methods = build_call_chains(entries, method_map)
write_chains_to_csv(chains, full_methods)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?