JarvizのJSONLフォーマット
Jarviz(jarviz-graph)は1行1JSONオブジェクトの JSONL(JSON Lines) 形式でCall Graph情報を受け取ります。 各行に呼び出し関係を1エッジとして記録します。
{"appSetName":"myapp","artifactFileName":"myapp.jar","packageName":"com.example","className":"OrderService","methodName":"placeOrder","callerPackageName":"com.example","callerClassName":"OrderController","callerMethodName":"submitOrder"}
{"appSetName":"myapp","artifactFileName":"myapp.jar","packageName":"com.example","className":"PaymentService","methodName":"charge","callerPackageName":"com.example","callerClassName":"OrderService","callerMethodName":"placeOrder"}
6つの主要フィールドの意味は以下の通りです。
| フィールド | Java での意味 | COBOL でのマッピング |
|---|---|---|
appSetName | アプリケーション名 | システム名(固定値) |
artifactFileName | JARファイル名 | COBOLソースファイル名 |
packageName | パッケージ名 | Division("PROCEDURE"固定) |
className | クラス名 | プログラム名(PROGRAM-ID) |
methodName | 呼び出され側メソッド名 | 呼び出され側Paragraph名 |
callerClassName | 呼び出し元クラス名 | 呼び出し元プログラム名 |
callerMethodName | 呼び出し元メソッド名 | 呼び出し元Paragraph名 |
COBOLとJSONLフィールドのマッピング
COBOLの PERFORM PROCESS-PARA(MAIN-PARA から)を例に取ると、
以下のようにJSONLの1行に変換されます。
{"appSetName":"BILLING-SYSTEM","artifactFileName":"SAMPLE-PGM.cbl","packageName":"PROCEDURE","className":"SAMPLE-PGM","methodName":"PROCESS-PARA","callerPackageName":"PROCEDURE","callerClassName":"SAMPLE-PGM","callerMethodName":"MAIN-PARA"}
全体スクリプト
PART 03 で作成したCall Graph構築関数を利用して、 COBOLソースファイル群をJSONLに一括変換するスクリプトです。
import json
import re
import sys
from pathlib import Path
from collections import defaultdict
APP_SET_NAME = 'BILLING-SYSTEM' # ← システム名を変更してください
# ─────────────────────────────────────────────
# Paragraph 抽出
# ─────────────────────────────────────────────
def parse_paragraphs(text: str) -> list[str]:
proc_match = re.search(r'PROCEDURE\s+DIVISION', text, re.IGNORECASE)
if not proc_match:
return []
proc_text = text[proc_match.start():]
para_pattern = re.compile(r'^ ([A-Z0-9][A-Z0-9\-]*)\.\s*$', re.MULTILINE)
return [p for p in para_pattern.findall(proc_text) if p != 'DIVISION']
# ─────────────────────────────────────────────
# PERFORM エッジ抽出
# ─────────────────────────────────────────────
def parse_perform_edges(text: str, paragraphs: list[str]) -> list[tuple]:
para_set = set(paragraphs)
edges = []
proc_match = re.search(r'PROCEDURE\s+DIVISION', text, re.IGNORECASE)
if not proc_match:
return []
proc_text = text[proc_match.start():]
current_para = None
keywords = {'UNTIL', 'VARYING', 'THROUGH', 'THRU', 'TIMES', 'WITH', 'TEST'}
for line in proc_text.splitlines():
para_match = re.match(r'^ ([A-Z0-9][A-Z0-9\-]*)\.\s*$', line)
if para_match:
name = para_match.group(1)
if name != 'DIVISION':
current_para = name
continue
if current_para is None:
continue
perform_match = re.search(r'\bPERFORM\s+([A-Z0-9][A-Z0-9\-]*)', line, re.IGNORECASE)
if perform_match:
target = perform_match.group(1).upper()
if target not in keywords and target in para_set:
edges.append((current_para, target))
return edges
# ─────────────────────────────────────────────
# PROGRAM-ID 抽出
# ─────────────────────────────────────────────
def parse_program_id(text: str, fallback: str) -> str:
m = re.search(r'PROGRAM-ID\.\s+([A-Z0-9\-]+)', text, re.IGNORECASE)
return m.group(1).upper() if m else fallback.upper()
# ─────────────────────────────────────────────
# メイン変換
# ─────────────────────────────────────────────
def cobol_to_jsonl(src_dir: Path, output_file: Path) -> None:
records = []
for cobol_path in sorted(src_dir.glob('**/*.cbl')):
text = cobol_path.read_text(encoding='utf-8', errors='replace').upper()
program_id = parse_program_id(text, cobol_path.stem)
paragraphs = parse_paragraphs(text)
edges = parse_perform_edges(text, paragraphs)
for caller_para, callee_para in edges:
records.append({
'appSetName': APP_SET_NAME,
'artifactFileName': cobol_path.name,
'packageName': 'PROCEDURE',
'className': program_id,
'methodName': callee_para,
'callerPackageName': 'PROCEDURE',
'callerClassName': program_id,
'callerMethodName': caller_para,
})
with output_file.open('w', encoding='utf-8') as f:
for rec in records:
f.write(json.dumps(rec, ensure_ascii=False) + '\n')
print(f'✅ {len(records)} edges → {output_file}')
if __name__ == '__main__':
src = Path(sys.argv[1]) if len(sys.argv) > 1 else Path('src')
out = Path(sys.argv[2]) if len(sys.argv) > 2 else Path('cobol-callgraph.jsonl')
cobol_to_jsonl(src, out)
出力例
{"appSetName":"BILLING-SYSTEM","artifactFileName":"SAMPLE-PGM.cbl","packageName":"PROCEDURE","className":"SAMPLE-PGM","methodName":"INIT-PARA","callerPackageName":"PROCEDURE","callerClassName":"SAMPLE-PGM","callerMethodName":"MAIN-PARA"}
{"appSetName":"BILLING-SYSTEM","artifactFileName":"SAMPLE-PGM.cbl","packageName":"PROCEDURE","className":"SAMPLE-PGM","methodName":"PROCESS-PARA","callerPackageName":"PROCEDURE","callerClassName":"SAMPLE-PGM","callerMethodName":"MAIN-PARA"}
{"appSetName":"BILLING-SYSTEM","artifactFileName":"SAMPLE-PGM.cbl","packageName":"PROCEDURE","className":"SAMPLE-PGM","methodName":"END-PARA","callerPackageName":"PROCEDURE","callerClassName":"SAMPLE-PGM","callerMethodName":"MAIN-PARA"}
{"appSetName":"BILLING-SYSTEM","artifactFileName":"SAMPLE-PGM.cbl","packageName":"PROCEDURE","className":"SAMPLE-PGM","methodName":"READ-PARA","callerPackageName":"PROCEDURE","callerClassName":"SAMPLE-PGM","callerMethodName":"PROCESS-PARA"}
複数プログラムのまとめ方
プログラム間CALL(CALL 'SUBPGM01')も同じJSONLに含めることで、
複数プログラムを横断したCall Graphを1ファイルに収められます。
この場合、callerClassName(呼び出し元プログラム)とclassName(呼び出し先プログラム)が異なる行が生成されます。
💡 Java版との混在
Java版で生成したJSONLとCOBOL版のJSONLを単純にcatコマンドで結合するだけで、言語をまたいだ3Dマップを生成できます。classNameにJavaのクラス名とCOBOLのPROGRAM-IDが混在しても、Jarvizはそのまま処理します。
出力の検証
# 行数確認
wc -l cobol-callgraph.jsonl
# 各行が有効なJSONか検証
python -c "
import json, sys
errors = 0
with open('cobol-callgraph.jsonl') as f:
for i, line in enumerate(f, 1):
try:
json.loads(line)
except json.JSONDecodeError as e:
print(f'Line {i}: {e}'); errors += 1
print(f'Errors: {errors}')
"
# callerClassName の一覧(プログラム一覧)
python -c "
import json
programs = set()
with open('cobol-callgraph.jsonl') as f:
for line in f:
d = json.loads(line)
programs.add(d['callerClassName'])
for p in sorted(programs): print(p)
"
✅ 次の章では…
PART 05 では生成したJSONLをJarvizに読み込み、3Dの力線グラフで表示するセットアップ手順と、Java版との差異を解説します。