Part1では、Helidonの概要と移行のためのプロンプト準備について解説しました。Part 2では、Pythonスクリプトを使用して実際にコード変換を自動化し、Helidonプロジェクトとしてビルドするまでの手順を説明します。
3.5 ファイル分類スクリプトの作成 (Classifier)
AIに適切なプロンプトを渡すためには、各Javaファイルがどの役割(Controller, Service, Repository, Entity)を持っているかを判別する必要があります。
3.5.1 classify.py の作成
nano classify.py
import os
def classify(path):
text = open(path).read()
if path.endswith("pom.xml"):
return "pom"
if "@RestController" in text:
return "controller"
if "JpaRepository" in text or "CrudRepository" in text:
return "repo"
if "@Service" in text or "@Component" in text:
return "service"
if "@Entity" in text:
return "entity"
return "java"
if __name__ == "__main__":
base_dir = "../spring-petclinic"
for root, dirs, files in os.walk(base_dir):
for file in files:
if file.endswith(".java") or file.endswith("pom.xml"):
path = os.path.join(root, file)
ftype = classify(path)
print(f"{path}:{ftype}")
動作確認:
python3 classify.py
3.6 変換実行スクリプトの作成
OpenAI API(または互換API)を呼び出してコードを変換するメインスクリプトを作成します。
3.6.1 APIキーの設定
export OPENAI_API_KEY="YOUR_API_KEY"
3.6.2 convert.py の作成
nano convert.py
import os
from openai import OpenAI
from concurrent.futures import ThreadPoolExecutor, as_completed
from threading import Lock
import time
from datetime import datetime
# 環境変数から設定を読み込み
api_key = (os.getenv("OPENAI_API_KEY") or "").strip()
base_url = (os.getenv("OPENAI_BASE_URL") or "").strip()
model_name = os.getenv("MODEL_NAME", "gpt-4").strip()
if not api_key:
raise ValueError("OPENAI_API_KEY environment variable is not set")
client = OpenAI(
api_key=api_key,
base_url=base_url if base_url else None
)
PROMPTS = {
"controller": "prompts/controller.txt",
"repo": "prompts/repo.txt",
"service": "prompts/service.txt",
"entity": "prompts/entity.txt",
"pom": "prompts/pom.txt",
"java": "prompts/entity.txt" # fallback
}
# スレッドセーフなカウンタ
success_lock = Lock()
error_lock = Lock()
progress_lock = Lock()
success_count = 0
error_count = 0
completed_count = 0
start_time = None
def print_progress(completed, total):
global start_time
if total == 0: return
percentage = (completed / total) * 100
elapsed = time.time() - start_time if start_time else 0
status = f"[{completed}/{total}] ({percentage:.1f}%) | {int(elapsed)}s elapsed"
print(f"\r{status}", end="", flush=True)
def convert(path, ftype, max_retries=3):
file_start_time = time.time()
try:
prompt = open(PROMPTS[ftype]).read()
except KeyError:
# プロンプトが定義されていないタイプはスキップまたはデフォルト処理
return False, 0, f"No prompt for type: {ftype}"
code = open(path).read()
# コードサイズに応じたタイムアウト設定
code_size_kb = len(code) / 1024
timeout = min(int(60 + (code_size_kb * 2)), 600)
messages = [
{"role": "system", "content": prompt},
{"role": "user", "content": code},
]
last_error = None
for attempt in range(max_retries):
try:
if attempt > 0:
time.sleep(min(2 ** attempt, 10))
result = client.chat.completions.create(
model=model_name,
temperature=0,
messages=messages,
timeout=timeout
)
output = result.choices[0].message.content
# 出力ファイルの保存
out_path = "output/" + path.replace("../spring-petclinic/", "")
os.makedirs(os.path.dirname(out_path), exist_ok=True)
with open(out_path, "w") as f:
f.write(output)
elapsed = time.time() - file_start_time
return True, elapsed
except Exception as e:
last_error = e
# タイムアウト以外はリトライしない等の制御をここに追加可能
elapsed = time.time() - file_start_time
return False, elapsed, str(last_error)
def convert_with_counter(path, ftype, total):
global success_count, error_count, completed_count
result_data = convert(path, ftype)
with progress_lock:
completed_count += 1
if result_data[0]: # success
success_count += 1
else:
error_count += 1
print_progress(completed_count, total)
return result_data[0]
if __name__ == "__main__":
import sys
# ファイルリストの読み込み
tasks = []
if os.path.exists("file-list.txt"):
with open("file-list.txt", "r") as f:
for line in f:
if ":" in line:
path, ftype = line.strip().split(":", 1)
tasks.append((path.strip(), ftype.strip()))
total = len(tasks)
max_workers = int(os.getenv("MAX_WORKERS", "2"))
print(f"Found {total} files to convert. Using {max_workers} workers.")
start_time = time.time()
print_progress(0, total)
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [executor.submit(convert_with_counter, path, ftype, total) for path, ftype in tasks]
for future in as_completed(futures):
pass # 結果はコールバック内で処理済み
total_time = time.time() - start_time
print(f"\n\nDone. Success: {success_count}, Failed: {error_count}, Time: {total_time:.1f}s")
3.7 変換プロセスの実行
3.7.1 変換対象リストの生成
python3 classify.py > file-list.txt
3.7.2 自動変換の実行
python3 convert.py
変換されたコードは converter/output/ ディレクトリに出力されます。
3.8 Helidonプロジェクトの作成とコード統合
変換されたコードを受け入れるためのHelidon MPプロジェクトの雛形を作成します。
cd ..
mvn archetype:generate -DinteractiveMode=false \
-DarchetypeGroupId=io.helidon.archetypes \
-DarchetypeArtifactId=helidon-mp
生成されたプロジェクトに、converter/output/ 内のコードをコピーして統合します。その後、ビルドと実行を行います。
mvn package
java -jar target/*.jar
3.9 結果
変換が成功すれば、Spring Bootアプリケーションと同等の機能を持つHelidonアプリケーションが起動します。
4. 結論
AIは開発者のワークフローにおける強力なパートナーとなりつつあります。フレームワークの移行という、従来であれば多大な労力と時間を要したタスクも、AIの支援により大幅に効率化され、コスト削減が可能になります。
今回紹介した手法はSpring BootからHelidonへの移行だけでなく、適切なプロンプトを用意することで、レガシーなJava EE (javax.*) からモダンなJakarta EEへの移行など、様々なモダナイゼーションのシナリオに応用可能です。
関連リンク
- Spring Pets – 検証に使用したテストプロジェクト
- Context-aware Converter
- Incremental Converter
