2025年5月

1주차
온보딩

모두의연구소와 아이펠 교육 철학의 이해

아이펠을 AI 학교라고 부르는 이유, 커뮤니티 기반 성장형 교육이란 무엇인지 이해하기

아이펠 교육 방식 이해하기

게임형 퀘스트 유형 설명 및 실습하기

과정에서 목표로 하는 성장 이미지 공유하기

개발자의 기초 소양 갖추기

GitHub 사용 방법을 이해하고 실습하기

터미널로 배우는 리눅스 운영체제 이해하기

개발자의 글쓰기 전략과 필요성 이해하기

2~3주차
딥러닝 기초
머신러닝과 딥러닝 일반

머신러닝의 전반적인 프로세스와 머신러닝의 특징 및 기본 개념 이해하기

데이터 정제, 시각화, 모델 설계에 필요한 다양한 라이브러리 학습하기

기본 통계 개념

머신러닝을 위한 기본적인 통계 개념 학습하기

인공신경망과 딥러닝

인공신경망의 구조와 딥러닝의 기본적인 원리 학습하기

머신러닝과 딥러닝의 학습 프로세스 이해

딥러닝 모델 학습에서 발생 가능한 문제점을 이해하고, 해결 방안 도출하기

평가 지표를 설정하고 분석하는 법 이해하기
3~4주차
딥러닝 심화
다양한 딥러닝 모델 End to End 학습

데이터 준비와 전처리의 방법론 이해하고 실습하기

CNN, RNN~ Transformer 까지 계열 별 기본 모델을 학습시키면서 원리 이해하기

Task 별 딥러닝 적용의 원인을 이해하고 구현하기

데이터에 적절한 방법론의 종류를 이해하고 적용하기

DLthon

주어진 task로 프로젝트 수행

주어진 데이터를 활용하여 팀 프로젝트 완성하기

Task에 맞는 EDA와 전처리 실습하기

적절한 모델 선정하기

평가를 위한 지표 설정과 결과 분석하기

성능향상을 위한 논리적인 방법론 선택하기

심사를 통해 프로젝트 결과물 피드백 받기

8~11주차
딥러닝 고도화
컴퓨터비전(CV) 전공

깊은 레이어 모델을 학습할 때 문제점과 ResNet모델의 이론적 배경 이해 및 구현하기

데이터 증강 원리 이해하고 활용하기

Detection과 Segmentation 이론적 배경과 기술 학습하기

CNN을 분석하기 위한 XAI Tool 구현과 적용하기

OCR 기본 원리 이해하고 구현하기

CV 최신 트렌드 모델 이해하기

자연어처리(NLP) 전공

자연어 데이터와 임베딩 이해하기

토크나이징 원리와 적용법 이해하기

자연어 데이터의 벡터화 및 수치화하기

Attention 이해하기

Transformer 이해하고 구현하기

Hugging Face 모델 사용법 이해하고 학습 루프 구현하기

NLP 최신 트렌드 모델 이해하기

12~13주차
딥러닝 응용
MLOps 프로세스 학습

MLOps의 이해와 트렌드

머신러닝 도입 배경과 MLOps의 개념을 학습하며 MLOps 생태계와 주요 도구들을 탐구.

구현 전략과 성숙도 단계, 그리고 시장 동향과 미래 발전 가능성까지 통합적인 이해.

도커 활용과 컨테이너 관리

Docker의 설치와 기본 개념을 익히고, 컨테이너 환경 구성 및 최적화 전략 실습.

도커 스웜과 컴포즈를 활용한 오케스트레이션 기법 학습, 부하 분산 및 Kubernetes 기초 탐색.

GCP와 Vertex AI로 확장하기

Google Cloud Platform의 서비스와 BigQuery를 활용한 데잍터 처리.

Vertex AI를 활용한 모델 학습, 서빙, 그리고 클라우드 기반 AI 파이프라인 구축.

Airflow로 워크플로 자동화

에어플로 환경 구성과 DAG 설정을 중심으로, 데이터 워크플로 관리 실습.

복합적인 데이터 아키텍쳐 설계와 에어플로를 활용한 대규모 작업 관리.

데이터 및 모델 파이프라인

CI/CD 파이프라인의 기초부터 자동화된 빌드와 배포를 학습.

Vertex AI 기반의 파이프라인 구축 및 온라인 예측 배포를 통한 실무 지향적 훈련.

실전 프로젝트: 통합 MLOps

데이터 전처리, 모델 훈련, 배포를 포함한 파이프라인 설계와 개선 실습.

Kubeflow pipeline 도입 및 성능 모니터링, 최적화와 템플릿 자동화를 통한 실제 적용 경험

Mini AIFFELthon
해결하고자 하는 task로 프로젝트 수행

목표 정의 및 자료 확보를 통한 연구 계획 수립하기

주체에 적합한 데이터 탐색과 가공 진행하기

최적의 알고리즘 결정하기

검증을 위한 메트릭 선정과 산출물 해석하기

효율 개선을 위한 체계적인 접근법 채택하기

평가를 통해 최종 결과물 리뷰 받기

14~16주차
논문과 친해지기
논문 읽기

논문을 빠르게 읽고 효과적으로 이해하는 노하우 배우기

전공 별 트렌드에 맞는 HOT한 모델 이해하기

CV, NLP를 아우르는 MultiModal 모델 이해하기

논문 쓰는 노하우 배우기

CV: 3D gaussian splatting - Instance segmentation - ViT - DETR (+Dino)

NLP:Transformer-RAG Survey paper - QLoRA (+LoRA) - Deepseek-r1 (+Deepseek-v3)

공통: CLIP - Masked AutoEncoder - Segment anything - LLaVA (LLaVA-o1)

  • (논문은 트랜드를 반영하여 달라질 수 있음)
    17~24주차
    AI 연구 프로젝트
    Project Planning과 PoC LAB

아이디어를 구현하기 위한 합리적인 프로젝트 계획하기

계획의 구현 가능성과 문제점을 파악하고, 주어진 환경 자원에 맞춰 고도화하기

Project Managing

팀장, 팀원으로 기한 내 계획된 프로젝트를 완수하기 위한 시간관리, 자원 배분 등 매니징 능력 등 소프트스킬 기르기

Project 실행 및 문제해결 역량 기르기

파이프라인 구성과 커스텀 함수 등 다양한 구현 능력 기르기

구현 중 발생하는 다양한 문제 해결 능력 기르기

CONCAT()

-- 모든 프로세스 조회
SHOW FULL PROCESSLIST;
-- 락테이블 조회
select * from information_schema.innodb_locks;
-- 대기중인 락 조회
select * from information_schema.innodb_lock_waits;
-- 트랜젝션조회
select * FROM information_schema.INNODB_TRX;

-- 해당 프로세스 종료
kill 495299;

-- 종합쿼리
SELECT * FROM information_schema.PROCESSLIST
WHERE Command <> 'Sleep'
AND USER NOT IN ('system user', 'event_scheduler')
ORDER BY TIME DESC
;

시스템 문제를 체크하기 위해서 DB에 SQL과 기준, 체크 스케줄을 등록하고 배치를 수행하여 문제가 발생하였을 떄 담당자에게 메일을 보내는 기능을 개발하려고 합니다. Java 기반으로 유사한 오픈 소스 프로젝트 찾아 주세요.

Quartz: 가장 널리 사용되는 Java 스케줄러 라이브러리 중 하나입니다. Cron 표현식을 사용하여 정교한 스케줄링이 가능하며, Job 및 Trigger 개념을 통해 실행할 작업을 정의하고 관리할 수 있습니다. SQL 실행, 결과 검증, 메일 발송 로직을 Job으로 구현하여 스케줄링할 수 있습니다.

https://camel.apache.org/

OpenNMS
https://github.com/OpenNMS/opennms

✅ 2. JobRunr
https://www.jobrunr.io/en/
특징:
백그라운드 작업 처리: Java 8 람다 표현식을 사용하여 백그라운드 작업을 쉽게 정의하고 실행할 수 있습니다.
스케줄링 기능: enqueue, schedule, scheduleRecurrently 메서드를 통해 작업을 즉시 실행하거나 예약할 수 있습니다.
대시보드 제공: 내장된 대시보드를 통해 작업 상태, 진행 상황, 실패 내역 등을 실시간으로 모니터링할 수 있습니다.
다양한 저장소 지원: 관계형 데이터베이스(RDBMS), MongoDB, Redis 등 다양한 저장소와 통합할 수 있습니다.
적합한 경우:
정기적인 데이터베이스 상태 점검 작업을 예약하고, 결과에 따라 이메일 알림을 보내는 시스템을 구축할 때.
작업의 상태를 시각적으로 모니터링하고 관리하고자 할 때.

  1. Dolphin Scheduler (Apache Incubator, 中国 개발팀 참여)
    https://github.com/apache/dolphinscheduler
    기능: 분산 스케줄링 시스템으로, SQL 작업을 정의하고 주기적으로 실행할 수 있으며, 실패 시 이메일/알림 발송 가능
    특징:
    웹 UI를 통해 작업 흐름(Workflow)을 정의하고 스케줄링
    MySQL, PostgreSQL 등 다양한 DB 지원
    중국 개발팀이 주도한 프로젝트로 중국어 문서 풍부
  1. Alibaba Canal (Alibaba 오픈소스)
    https://github.com/apache/dolphinscheduler
    기능: MySQL Binlog 기반의 데이터 동기화/모니터링 도구
    유사점: DB 변경 사항을 감지하고 알림 가능 (확장 개발 필요)
    참고: 주로 CDC(Change Data Capture)용이지만, 커스텀 체크 로직 추가 가능
  2. HertzBeat (中国 Dromara 커뮤니티)
    https://github.com/apache/hertzbeat
    기능: 오픈소스 실시간 모니터링 시스템으로 DB, API, 서비스 건강 상태 체크
    특징:
    지원 DB: MySQL, Oracle, PostgreSQL 등
    임계값 초과 시 이메일/웹훅 알림
    중국 개발자 중심의 활발한 커뮤니티

https://github.com/dromara/Jpom

  1. Ward (간단한 모니터링 툴)
    기능: 서버 및 DB 상태 모니터링 대시보드

참고: 알림 기능은 제한적이지만, 오픈소스라 커스터마이징 가능

  1. Jpom (Dromara)
    웹 사이
    https://github.com/dromara/Jpom
    기능: Java 프로젝트 운영 관리 도구로, 배치 및 모니터링 기능 포함

유사점: 스크립트 실행 및 알림 기능 지원

SQLeonardo

Java 기반 SQL 모니터링 및 관리 도구
스케줄링 기능과 알림 통합 가능
https://sourceforge.net/projects/sqleonardo/

Java & iBatis 소스 코드 분석 및 시각화 도구 개발

개발 환경 구축 및 코드 생성

아래에서는 프로젝트 환경 구축 및 코드 생성을 진행하겠습니다. 요구사항에 맞춰 프로젝트 구조, 데이터베이스 스키마, 백엔드/프론트엔드 코드를 순차적으로 제공합니다.

1. 프로젝트 구조

itf/
├── backend/                        # 백엔드 프로젝트
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/com/viw/itf/
│   │   │   │   ├── ItfApplication.java
│   │   │   │   ├── config/        # 설정 관련 클래스
│   │   │   │   ├── controller/    # REST API 컨트롤러
│   │   │   │   ├── entity/        # JPA 엔티티
│   │   │   │   ├── repository/    # 데이터 액세스 계층
│   │   │   │   ├── service/       # 비즈니스 로직
│   │   │   │   ├── parser/        # 소스 코드 파서
│   │   │   │   ├── analyzer/      # 의존성 분석
│   │   │   │   ├── diagram/       # 다이어그램 생성
│   │   │   │   └── util/          # 유틸리티 클래스
│   │   │   └── resources/
│   │   │       ├── application.properties
│   │   │       ├── application-local.properties
│   │   │       └── schema.sql     # 데이터베이스 스키마
│   │   └── test/                  # 테스트 코드
│   └── pom.xml                    # Maven 설정
└── frontend/                      # 프론트엔드 프로젝트
    ├── public/
    ├── src/
    │   ├── assets/                # 정적 자원
    │   ├── components/            # 재사용 컴포넌트
    │   ├── pages/                 # 페이지 컴포넌트
    │   ├── services/              # API 서비스
    │   ├── store/                 # MobX 상태 관리
    │   ├── types/                 # TypeScript 타입 정의
    │   ├── utils/                 # 유틸리티 함수
    │   ├── App.tsx
    │   └── index.tsx
    ├── package.json
    └── tsconfig.json              # TypeScript 설정

2. 데이터베이스 스키마 설계

schema.sql 파일로 제공되는, MariaDB 데이터베이스에 필요한 테이블 구조입니다:

-- 프로젝트 메타데이터 테이블
CREATE TABLE project (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    description TEXT,
    java_source_path VARCHAR(500) NOT NULL,
    ibatis_source_path VARCHAR(500) NOT NULL,
    analysis_date DATETIME DEFAULT CURRENT_TIMESTAMP,
    version VARCHAR(50)
);

-- 패키지 정보 테이블
CREATE TABLE package (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    name VARCHAR(255) NOT NULL,
    full_path VARCHAR(500) NOT NULL,
    FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE
);

-- 클래스 정보 테이블
CREATE TABLE class (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    package_id INT NOT NULL,
    name VARCHAR(255) NOT NULL,
    full_name VARCHAR(500) NOT NULL,
    file_path VARCHAR(1000) NOT NULL,
    class_type ENUM('CLASS', 'INTERFACE', 'ENUM', 'ANNOTATION') NOT NULL,
    access_modifier VARCHAR(20),
    extends_class VARCHAR(500),
    implements_interfaces TEXT,
    is_component BOOLEAN DEFAULT FALSE,
    component_type VARCHAR(100),
    FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE,
    FOREIGN KEY (package_id) REFERENCES package(id) ON DELETE CASCADE
);

-- 메소드 정보 테이블
CREATE TABLE method (
    id INT AUTO_INCREMENT PRIMARY KEY,
    class_id INT NOT NULL,
    name VARCHAR(255) NOT NULL,
    signature VARCHAR(1000) NOT NULL,
    return_type VARCHAR(255),
    parameters TEXT,
    access_modifier VARCHAR(20),
    is_static BOOLEAN DEFAULT FALSE,
    start_line INT,
    end_line INT,
    FOREIGN KEY (class_id) REFERENCES class(id) ON DELETE CASCADE
);

-- SQL 쿼리 정보 테이블
CREATE TABLE sql_query (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    namespace VARCHAR(255) NOT NULL,
    query_id VARCHAR(255) NOT NULL,
    query_type ENUM('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'PROCEDURE', 'OTHER') NOT NULL,
    query_text TEXT NOT NULL,
    file_path VARCHAR(1000) NOT NULL,
    FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE
);

-- 의존성 정보 테이블
CREATE TABLE dependency (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    source_type ENUM('CLASS', 'METHOD', 'SQL') NOT NULL,
    source_id INT NOT NULL,
    target_type ENUM('CLASS', 'METHOD', 'SQL') NOT NULL,
    target_id INT NOT NULL,
    dependency_type VARCHAR(50) NOT NULL,  -- CALLS, EXTENDS, IMPLEMENTS, USES, SQL_CALL
    line_number INT,
    FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE
);

-- 다이어그램 메타데이터 테이블
CREATE TABLE diagram (
    id INT AUTO_INCREMENT PRIMARY KEY,
    project_id INT NOT NULL,
    name VARCHAR(255) NOT NULL,
    diagram_type ENUM('SEQUENCE', 'CLASS', 'COMPONENT', 'PACKAGE', 'ACTIVITY', 'FLOWCHART', 'MINDMAP', 'UML', 'CONTAINER') NOT NULL,
    description TEXT,
    creation_date DATETIME DEFAULT CURRENT_TIMESTAMP,
    last_modified DATETIME DEFAULT CURRENT_TIMESTAMP,
    author VARCHAR(100),
    layout_data TEXT,
    FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE
);

-- 다이어그램 요소 테이블
CREATE TABLE diagram_element (
    id INT AUTO_INCREMENT PRIMARY KEY,
    diagram_id INT NOT NULL,
    element_type VARCHAR(50) NOT NULL, -- CLASS, METHOD, PACKAGE, etc.
    element_id INT NOT NULL,  -- Reference to actual element
    x_position INT,
    y_position INT,
    width INT,
    height INT,
    custom_properties TEXT,  -- JSON format for additional properties
    FOREIGN KEY (diagram_id) REFERENCES diagram(id) ON DELETE CASCADE
);

-- 다이어그램 관계 테이블
CREATE TABLE diagram_relationship (
    id INT AUTO_INCREMENT PRIMARY KEY,
    diagram_id INT NOT NULL,
    source_element_id INT NOT NULL,
    target_element_id INT NOT NULL,
    relationship_type VARCHAR(50) NOT NULL,  -- ASSOCIATION, INHERITANCE, etc.
    label VARCHAR(255),
    control_points TEXT,  -- JSON format for line control points
    custom_properties TEXT,  -- JSON format for additional properties
    FOREIGN KEY (diagram_id) REFERENCES diagram(id) ON DELETE CASCADE,
    FOREIGN KEY (source_element_id) REFERENCES diagram_element(id) ON DELETE CASCADE,
    FOREIGN KEY (target_element_id) REFERENCES diagram_element(id) ON DELETE CASCADE
);

-- 인덱스 생성
CREATE INDEX idx_class_project ON class(project_id);
CREATE INDEX idx_method_class ON method(class_id);
CREATE INDEX idx_sql_project ON sql_query(project_id);
CREATE INDEX idx_dependency_project ON dependency(project_id);
CREATE INDEX idx_dependency_source ON dependency(source_type, source_id);
CREATE INDEX idx_dependency_target ON dependency(target_type, target_id);
CREATE INDEX idx_diagram_project ON diagram(project_id);
CREATE INDEX idx_diagram_element_diagram ON diagram_element(diagram_id);
CREATE INDEX idx_diagram_relationship_diagram ON diagram_relationship(diagram_id);

3. 백엔드 구현

3.1 Maven 프로젝트 설정 (pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cohttp://m.viw.itf</groupId>
    <artifactId>com_viw_itf</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>itf</name>
    <description>Source code analyzer and diagram generator</description>
    
    <properties>
        <java.version>11</java.version>
        <javaparser.version>3.24.0</javaparser.version>
        <structurizr.version>1.16.1</structurizr.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Starters -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        
        <!-- Database -->
        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <!-- Development Tools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- Source Code Analysis -->
        <dependency>
            <groupId>com.github.javaparser</groupId>
            <artifactId>javaparser-symbol-solver-core</artifactId>
            <version>${javaparser.version}</version>
        </dependency>
        
        <!-- Structurizr for Diagrams -->
        <dependency>
            <groupId>com.structurizr</groupId>
            <artifactId>structurizr-core</artifactId>
            <version>${structurizr.version}</version>
        </dependency>
        <dependency>
            <groupId>com.structurizr</groupId>
            <artifactId>structurizr-client</artifactId>
            <version>${structurizr.version}</version>
        </dependency>
        <dependency>
            <groupId>com.structurizr</groupId>
            <artifactId>structurizr-plantuml</artifactId>
            <version>${structurizr.version}</version>
        </dependency>
        <dependency>
            <groupId>com.structurizr</groupId>
            <artifactId>structurizr-mermaid</artifactId>
            <version>${structurizr.version}</version>
        </dependency>
        <dependency>
            <groupId>com.structurizr</groupId>
            <artifactId>structurizr-export</artifactId>
            <version>${structurizr.version}</version>
        </dependency>
        <dependency>
            <groupId>com.structurizr</groupId>
            <artifactId>structurizr-autolayout</artifactId>
            <version>${structurizr.version}</version>
        </dependency>
        
        <!-- XML Parser for iBatis -->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>
        
        <!-- JSON Processing -->
        <dependency>
            <groupId>cohttp://m.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        
        <!-- Testing -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

3.2 애플리케이션 설정 (application.properties)

# Application Name
spring.application.name=itf
server.port=8080

# Database Configuration
spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/itf
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver

# JPA/Hibernate Configuration
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect

# Logging Configuration
logging.level.cohttp://m.viw.itf=DEBUG
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

# Custom Properties
app.source.java-path=C:/work_back/viw_ep_edi/src/main/java/com/viw/esp/online
app.source.ibatis-path=C:/work_back/viw_erp_wms/src/main/resources/sql

3.3 메인 애플리케이션 클래스

package cohttp://m.viw.itf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
public class ItfApplication extends SpringBootServletInitializer {
    
    public static void main(String[] args) {
        SpringApplication.run(ItfApplication.class, args);
    }
}

3.4 설정 클래스

package cohttp://m.viw.itf.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.nio.file.Path;
import java.nio.file.Paths;

@Configuration
public class AppConfig implements WebMvcConfigurer {

    @Value("${app.source.java-path}")
    private String javaSourcePath;

    @Value("${app.source.ibatis-path}")
    private String ibatisSourcePath;

    @Bean
    public Path javaSourceDirectory() {
        return Paths.get(javaSourcePath);
    }

    @Bean
    public Path ibatisSourceDirectory() {
        return Paths.get(ibatisSourcePath);
    }
    
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:3000")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true);
    }
}

3.5 엔티티 클래스

Project.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "project")
@Data
public class Project {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @Column(nullable = false)
    private String name;
    
    private String description;
    
    @Column(name = "java_source_path", nullable = false)
    private String javaSourcePath;
    
    @Column(name = "ibatis_source_path", nullable = false)
    private String ibatisSourcePath;
    
    @Column(name = "analysis_date")
    private LocalDateTime analysisDate;
    
    private String version;
    
    @OneToMany(mappedBy = "project", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Package> packages = new ArrayList<>();
    
    @OneToMany(mappedBy = "project", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<SqlQuery> sqlQueries = new ArrayList<>();
    
    @OneToMany(mappedBy = "project", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Dependency> dependencies = new ArrayList<>();
    
    @OneToMany(mappedBy = "project", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Diagram> diagrams = new ArrayList<>();
}

Package.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "package")
@Data
public class Package {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "project_id", nullable = false)
    private Project project;
    
    @Column(nullable = false)
    private String name;
    
    @Column(name = "full_path", nullable = false)
    private String fullPath;
    
    @OneToMany(mappedBy = "pkg", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<CodeClass> classes = new ArrayList<>();
}

CodeClass.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "class")
@Data
public class CodeClass {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "project_id", nullable = false)
    private Project project;
    
    @ManyToOne
    @JoinColumn(name = "package_id", nullable = false)
    private Package pkg;
    
    @Column(nullable = false)
    private String name;
    
    @Column(name = "full_name", nullable = false)
    private String fullName;
    
    @Column(name = "file_path", nullable = false)
    private String filePath;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "class_type", nullable = false)
    private ClassType classType;
    
    @Column(name = "access_modifier")
    private String accessModifier;
    
    @Column(name = "extends_class")
    private String extendsClass;
    
    @Column(name = "implements_interfaces")
    private String implementsInterfaces;
    
    @Column(name = "is_component")
    private Boolean isComponent = false;
    
    @Column(name = "component_type")
    private String componentType;
    
    @OneToMany(mappedBy = "codeClass", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Method> methods = new ArrayList<>();
    
    public enum ClassType {
        CLASS, INTERFACE, ENUM, ANNOTATION
    }
}

Method.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "method")
@Data
public class Method {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "class_id", nullable = false)
    private CodeClass codeClass;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false)
    private String signature;
    
    @Column(name = "return_type")
    private String returnType;
    
    private String parameters;
    
    @Column(name = "access_modifier")
    private String accessModifier;
    
    @Column(name = "is_static")
    private Boolean isStatic = false;
    
    @Column(name = "start_line")
    private Integer startLine;
    
    @Column(name = "end_line")
    private Integer endLine;
}

SqlQuery.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "sql_query")
@Data
public class SqlQuery {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "project_id", nullable = false)
    private Project project;
    
    @Column(nullable = false)
    private String namespace;
    
    @Column(name = "query_id", nullable = false)
    private String queryId;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "query_type", nullable = false)
    private QueryType queryType;
    
    @Column(name = "query_text", nullable = false, columnDefinition = "TEXT")
    private String queryText;
    
    @Column(name = "file_path", nullable = false)
    private String filePath;
    
    public enum QueryType {
        SELECT, INSERT, UPDATE, DELETE, PROCEDURE, OTHER
    }
}

Dependency.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "dependency")
@Data
public class Dependency {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "project_id", nullable = false)
    private Project project;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "source_type", nullable = false)
    private ElementType sourceType;
    
    @Column(name = "source_id", nullable = false)
    private Integer sourceId;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "target_type", nullable = false)
    private ElementType targetType;
    
    @Column(name = "target_id", nullable = false)
    private Integer targetId;
    
    @Column(name = "dependency_type", nullable = false)
    private String dependencyType;
    
    @Column(name = "line_number")
    private Integer lineNumber;
    
    public enum ElementType {
        CLASS, METHOD, SQL
    }
}

Diagram.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "diagram")
@Data
public class Diagram {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "project_id", nullable = false)
    private Project project;
    
    @Column(nullable = false)
    private String name;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "diagram_type", nullable = false)
    private DiagramType diagramType;
    
    private String description;
    
    @Column(name = "creation_date")
    private LocalDateTime creationDate;
    
    @Column(name = "last_modified")
    private LocalDateTime lastModified;
    
    private String author;
    
    @Column(name = "layout_data", columnDefinition = "TEXT")
    private String layoutData;
    
    @OneToMany(mappedBy = "diagram", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<DiagramElement> elements = new ArrayList<>();
    
    @OneToMany(mappedBy = "diagram", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<DiagramRelationship> relationships = new ArrayList<>();
    
    public enum DiagramType {
        SEQUENCE, CLASS, COMPONENT, PACKAGE, ACTIVITY, FLOWCHART, MINDMAP, UML, CONTAINER
    }
}

DiagramElement.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "diagram_element")
@Data
public class DiagramElement {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "diagram_id", nullable = false)
    private Diagram diagram;
    
    @Column(name = "element_type", nullable = false)
    private String elementType;
    
    @Column(name = "element_id", nullable = false)
    private Integer elementId;
    
    @Column(name = "x_position")
    private Integer xPosition;
    
    @Column(name = "y_position")
    private Integer yPosition;
    
    private Integer width;
    
    private Integer height;
    
    @Column(name = "custom_properties", columnDefinition = "TEXT")
    private String customProperties;
}

DiagramRelationship.java:

package cohttp://m.viw.itf.entity;

import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "diagram_relationship")
@Data
public class DiagramRelationship {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    
    @ManyToOne
    @JoinColumn(name = "diagram_id", nullable = false)
    private Diagram diagram;
    
    @ManyToOne
    @JoinColumn(name = "source_element_id", nullable = false)
    private DiagramElement sourceElement;
    
    @ManyToOne
    @JoinColumn(name = "target_element_id", nullable = false)
    private DiagramElement targetElement;
    
    @Column(name = "relationship_type", nullable = false)
    private String relationshipType;
    
    private String label;
    
    @Column(name = "control_points", columnDefinition = "TEXT")
    private String controlPoints;
    
    @Column(name = "custom_properties", columnDefinition = "TEXT")
    private String customProperties;
}

3.6 리포지토리 인터페이스

ProjectRepository.java:

package cohttp://m.viw.itf.repository;

import cohttp://m.viw.itf.entity.Project;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProjectRepository extends JpaRepository<Project, Integer> {
}

PackageRepository.java:

package cohttp://m.viw.itf.repository;

import cohttp://m.viw.itf.entity.Package;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface PackageRepository extends JpaRepository<Package, Integer> {
    
    List<Package> findByProjectId(Integer projectId);
    
    Optional<Package> findByProjectIdAndFullPath(Integer projectId, String fullPath);
}

CodeClassRepository.java:

package cohttp://m.viw.itf.repository;

import cohttp://m.viw.itf.entity.CodeClass;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface CodeClassRepository extends JpaRepository<CodeClass, Integer> {
    
    List<CodeClass> findByProjectId(Integer projectId);
    
    List<CodeClass> findByPkgId(Integer packageId);
    
    Optional<CodeClass> findByProjectIdAndFullName(Integer projectId, String fullName);
}

MethodRepository.java:

package cohttp://m.viw.itf.repository;

import cohttp://m.viw.itf.entity.Method;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface MethodRepository extends JpaRepository<Method, Integer> {
    
    List<Method> findByCodeClassId(Integer classId);
}

SqlQueryRepository.java:

package cohttp://m.viw.itf.repository;

import cohttp://m.viw.itf.entity.SqlQuery;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface SqlQueryRepository extends JpaRepository<SqlQuery, Integer> {
    
    List<SqlQuery> findByProjectId(Integer projectId);
    
    Optional<SqlQuery> findByProjectIdAndNamespaceAndQueryId(Integer projectId, String namespace, String queryId);
}

DependencyRepository.java:

package cohttp://m.viw.itf.repository;

import cohttp://m.viw.itf.entity.Dependency;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface DependencyRepository extends JpaRepository<Dependency, Integer> {
    
    List<Dependency> findByProjectId(Integer projectId);
    
    List<Dependency> findByProjectIdAndSourceTypeAndSourceId(
            Integer projectId, Dependency.ElementType sourceType, Integer sourceId);
    
    List<Dependency> findByProjectIdAndTargetTypeAndTargetId(
            Integer projectId, Dependency.ElementType targetType, Integer targetId);
    
    @Query("SELECT d FROM Dependency d WHERE d.project.id = :projectId AND " +
           "((d.sourceType = :type AND d.sourceId = :id) OR " +
           "(d.targetType = :type AND d.targetId = :id))")
    List<Dependency> findAllByElement(
            @Param("projectId") Integer projectId,
            @Param("type") Dependency.ElementType type,
            @Param("id") Integer id);
}

DiagramRepository.java:

package cohttp://m.viw.itf.repository;

import cohttp://m.viw.itf.entity.Diagram;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface DiagramRepository extends JpaRepository<Diagram, Integer> {
    
    List<Diagram> findByProjectId(Integer projectId);
    
    List<Diagram> findByProjectIdAndDiagramType(Integer projectId, Diagram.DiagramType diagramType);
}

3.7 소스 코드 파서 구현

JavaSourceParser.java:

package cohttp://m.viw.itf.parser;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ParserConfiguration;
import cohttp://m.github.javaparser.ast.CompilationUnit;
import cohttp://m.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import cohttp://m.github.javaparser.ast.body.MethodDeclaration;
import cohttp://m.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import cohttp://m.viw.itf.entity.*;
import cohttp://m.viw.itf.entity.Package;
import cohttp://m.viw.itf.repository.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.FileNotFoundException;
import java.nio.file.Path;
import java.util.Optional;

@Component
@RequiredArgsConstructor
@Slf4j
public class JavaSourceParser {

    private final ProjectRepository projectRepository;
    private final PackageRepository packageRepository;
    private final CodeClassRepository codeClassRepository;
    private final MethodRepository methodRepository;
    private final DependencyRepository dependencyRepository;
    private final Path javaSourceDirectory;
    
    private JavaParser javaParser;
    private Project currentProject;
    
    @Transactional
    public void parseJavaFiles(Project project) {
        this.currentProject = project;
        initializeJavaParser();
        
        File rootDir = javaSourceDirectory.toFile();
        if (!rootDir.exists() || !rootDir.isDirectory()) {
            log.error("Java source directory does not exist or is not a directory: {}", javaSourceDirectory);
            return;
        }
        
        processDirectory(rootDir);
    }
    
    private void initializeJavaParser() {
        // Configure symbol solver
        CombinedTypeSolver typeSolver = new CombinedTypeSolver();
        typeSolver.add(new ReflectionTypeSolver());
        typeSolver.add(new JavaParserTypeSolver(javaSourceDirectory.toFile()));
        
        JavaSymbolSolver symbolSolver = new JavaSymbolSolver(typeSolver);
        
        ParserConfiguration config = new ParserConfiguration();
        config.setSymbolResolver(symbolSolver);
        
        this.javaParser = new JavaParser(config);
    }
    
    private void processDirectory(File directory) {
        File[] files = directory.listFiles();
        if (files == null) return;
        
        for (File file : files) {
            if (file.isDirectory()) {
                processDirectory(file);
            } else if (file.getName().endsWith(".java")) {
                try {
                    log.debug("Parsing Java file: {}", file.getPath());
                    parseJavaFile(file);
                } catch (Exception e) {
                    log.error("Error parsing Java file: {}", file.getPath(), e);
                }
            }
        }
    }
    
    private void parseJavaFile(File file) throws FileNotFoundException {
        CompilationUnit cu = javaParser.parse(file).getResult().orElseThrow(() -> 
                new FileNotFoundException("Could not parse file: " + file.getPath()));
        
        String packageName = cu.getPackageDeclaration()
                .map(pd -> pd.getName().asString())
                .orElse("");
        
        // Get or create package
        Package pkg = getOrCreatePackage(packageName);
        
        // Visit class declarations
        cu.accept(new ClassVisitor(file, pkg), null);
    }
    
    private Package getOrCreatePackage(String packageName) {
        return packageRepository.findByProjectIdAndFullPath(currentProject.getId(), packageName)
                .orElseGet(() -> {
                    Package pkg = new Package();
                    pkg.setProject(currentProject);
                    pkg.setName(getSimplePackageName(packageName));
                    pkg.setFullPath(packageName);
                    return packageRepository.save(pkg);
                });
    }
    
    private String getSimplePackageName(String fullPath) {
        int lastDotIndex = fullPath.lastIndexOf('.');
        return lastDotIndex > 0 ? fullPath.substring(lastDotIndex + 1) : fullPath;
    }
    
    private class ClassVisitor extends VoidVisitorAdapter<Void> {
        private final File file;
        private final Package pkg;
        
        public ClassVisitor(File file, Package pkg) {
            this.file = file;
            this.pkg = pkg;
        }
        
        @Override
        public void visit(ClassOrInterfaceDeclaration classDecl, Void arg) {
            super.visit(classDecl, arg);
            
            try {
                String className = classDecl.getNameAsString();
                String fullClassName = pkg.getFullPath() + "." + className;
                
                CodeClass codeClass = new CodeClass();
                codeClass.setProject(currentProject);
                codeClass.setPkg(pkg);
                codeClass.setName(className);
                codeClass.setFullName(fullClassName);
                codeClass.setFilePath(file.getAbsolutePath());
                codeClass.setClassType(classDecl.isInterface() ? 
                        CodeClass.ClassType.INTERFACE : CodeClass.ClassType.CLASS);
                codeClass.setAccessModifier(classDecl.getAccessSpecifier().asString());
                
                // Handle extends
                if (classDecl.getExtendedTypes().size() > 0) {
                    codeClass.setExtendsClass(classDecl.getExtendedTypes().get(0).getNameAsString());
                }
                
                // Handle implements
                if (classDecl.getImplementedTypes().size() > 0) {
                    StringBuilder sb = new StringBuilder();
                    classDecl.getImplementedTypes().forEach(type -> 
                            sb.append(type.getNameAsString()).append(","));
                    codeClass.setImplementsInterfaces(sb.toString());
                }
                
                // Check for Spring annotations to identify components
                classDecl.getAnnotations().forEach(annotation -> {
                    String annotationName = annotation.getNameAsString();
                    if (annotationName.equals("Component") || 
                            annotationName.equals("Service") || 
                            annotationName.equals("Repository") || 
                            annotationName.equals("Controller") || 
                            annotationName.equals("RestController")) {
                        codeClass.setIsComponent(true);
                        codeClass.setComponentType(annotationName);
                    }
                });
                
                CodeClass savedClass = codeClassRepository.save(codeClass);
                
                // Process methods
                classDecl.getMethods().forEach(methodDecl -> 
                        processMethod(methodDecl, savedClass));
                
                // Process dependencies (inheritance, implementation)
                processClassDependencies(savedClass, classDecl);
                
            } catch (Exception e) {
                log.error("Error processing class: {}", classDecl.getNameAsString(), e);
            }
        }
        
        private void processMethod(MethodDeclaration methodDecl, CodeClass codeClass) {
            String methodName = methodDecl.getNameAsString();
            String signature = methodDecl.getDeclarationAsString(false, false, false);
            
            Method method = new Method();
            method.setCodeClass(codeClass);
            method.setName(methodName);
            method.setSignature(signature);
            method.setReturnType(methodDecl.getType().asString());
            method.setAccessModifier(methodDecl.getAccessSpecifier().asString());
            method.setIsStatic(methodDecl.isStatic());
            
            // Process parameters
            if (methodDecl.getParameters().size() > 0) {
                StringBuilder sb = new StringBuilder();
                methodDecl.getParameters().forEach(param -> 
                        sb.append(param.getType().asString()).append(" ")
                          .append(param.getNameAsString()).append(","));
                method.setParameters(sb.toString());
            }
            
            // Get line numbers
            methodDecl.getBegin().ifPresent(position -> method.setStartLine(position.line));
            methodDecl.getEnd().ifPresent(position -> method.setEndLine(position.line));
            
            Method savedMethod = methodRepository.save(method);
            
            // Process method dependencies
            processMethodDependencies(savedMethod, methodDecl);
        }
        
        private void processClassDependencies(CodeClass codeClass, ClassOrInterfaceDeclaration classDecl) {
            // If class extends another class
            if (classDecl.getExtendedTypes().size() > 0) {
                String extendedClassName = classDecl.getExtendedTypes().get(0).getNameAsString();
                // Try to find the extended class in the database
                Optional<CodeClass> extendedClass = codeClassRepository.findByProjectIdAndFullName(
                        currentProject.getId(), extendedClassName);
                
                extendedClass.ifPresent(target -> {
                    Dependency dependency = new Dependency();
                    dependency.setProject(currentProject);
                    dependency.setSourceType(Dependency.ElementType.CLASS);
                    dependency.setSourceId(codeClass.getId());
                    dependency.setTargetType(Dependency.ElementType.CLASS);
                    dependency.setTargetId(target.getId());
                    dependency.setDependencyType("EXTENDS");
                    dependencyRepository.save(dependency);
                });
            }
            
            // If class implements interfaces
            classDecl.getImplementedTypes().forEach(implementedType -> {
                String interfaceName = implementedType.getNameAsString();
                // Try to find the interface in the database
                Optional<CodeClass> interfaceClass = codeClassRepository.findByProjectIdAndFullName(
                        currentProject.getId(), interfaceName);
                
                interfaceClass.ifPresent(target -> {
                    Dependency dependency = new Dependency();
                    dependency.setProject(currentProject);
                    dependency.setSourceType(Dependency.ElementType.CLASS);
                    dependency.setSourceId(codeClass.getId());
                    dependency.setTargetType(Dependency.ElementType.CLASS);
                    dependency.setTargetId(target.getId());
                    dependency.setDependencyType("IMPLEMENTS");
                    dependencyRepository.save(dependency);
                });
            });
        }
        
        private void processMethodDependencies(Method method, MethodDeclaration methodDecl) {
            // Method dependency analysis would analyze method calls
            // This is a simplified version - actual implementation would be more complex
            methodDecl.findAll(cohttp://m.github.javaparser.ast.expr.MethodCallExpr.class)
                    .forEach(methodCall -> {
                        // Try to resolve the called method
                        // This is simplified - actual resolution is more complex
                        String calledMethodName = methodCall.getNameAsString();
                        
                        // For demonstration - find methods with the same name
                        methodRepository.findAll().stream()
                                .filter(m -> m.getName().equals(calledMethodName) && 
                                        !m.getId().equals(method.getId()))
                                .findFirst()
                                .ifPresent(targetMethod -> {
                                    Dependency dependency = new Dependency();
                                    dependency.setProject(currentProject);
                                    dependency.setSourceType(Dependency.ElementType.METHOD);
                                    dependency.setSourceId(method.getId());
                                    dependency.setTargetType(Dependency.ElementType.METHOD);
                                    dependency.setTargetId(targetMethod.getId());
                                    dependency.setDependencyType("CALLS");
                                    
                                    methodCall.getBegin().ifPresent(position -> 
                                            dependency.setLineNumber(position.line));
                                    
                                    dependencyRepository.save(dependency);
                                });
                    });
        }
    }
}

IBatisSourceParser.java:

package cohttp://m.viw.itf.parser;

import cohttp://m.viw.itf.entity.Dependency;
import cohttp://m.viw.itf.entity.Method;
import cohttp://m.viw.itf.entity.Project;
import cohttp://m.viw.itf.entity.SqlQuery;
import cohttp://m.viw.itf.repository.DependencyRepository;
import cohttp://m.viw.itf.repository.MethodRepository;
import cohttp://m.viw.itf.repository.SqlQueryRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.nio.file.Path;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
@RequiredArgsConstructor
@Slf4j
public class IBatisSourceParser {

    private final SqlQueryRepository sqlQueryRepository;
    private final MethodRepository methodRepository;
    private final DependencyRepository dependencyRepository;
    private final Path ibatisSourceDirectory;
    
    @Transactional
    public void parseIBatisFiles(Project project) {
        File rootDir = ibatisSourceDirectory.toFile();
        if (!rootDir.exists() || !rootDir.isDirectory()) {
            log.error("iBatis source directory does not exist or is not a directory: {}", ibatisSourceDirectory);
            return;
        }
        
        processDirectory(rootDir, project);
        analyzeJavaSqlDependencies(project);
    }
    
    private void processDirectory(File directory, Project project) {
        File[] files = directory.listFiles();
        if (files == null) return;
        
        for (File file : files) {
            if (file.isDirectory()) {
                processDirectory(file, project);
            } else if (file.getName().endsWith(".xml")) {
                try {
                    log.debug("Parsing iBatis file: {}", file.getPath());
                    parseIBatisFile(file, project);
                } catch (Exception e) {
                    log.error("Error parsing iBatis file: {}", file.getPath(), e);
                }
            }
        }
    }
    
    private void parseIBatisFile(File file, Project project) throws DocumentException {
        SAXReader reader = new SAXReader();
        Document document = reader.read(file);
        Element rootElement = document.getRootElement();
        
        if (!rootElement.getName().equals("sqlMap")) {
            return; // Not an iBatis SQL map file
        }
        
        String namespace = rootElement.attributeValue("namespace");
        if (namespace == null) {
            namespace = file.getName().replace(".xml", "");
        }
        
        // Process SELECT statements
        processQueryElements(rootElement.elements("select"), namespace, SqlQuery.QueryType.SELECT, file, project);
        
        // Process INSERT statements
        processQueryElements(rootElement.elements("insert"), namespace, SqlQuery.QueryType.INSERT, file, project);
        
        // Process UPDATE statements
        processQueryElements(rootElement.elements("update"), namespace, SqlQuery.QueryType.UPDATE, file, project);
        
        // Process DELETE statements
        processQueryElements(rootElement.elements("delete"), namespace, SqlQuery.QueryType.DELETE, file, project);
        
        // Process PROCEDURE statements
        processQueryElements(rootElement.elements("procedure"), namespace, SqlQuery.QueryType.PROCEDURE, file, project);
    }
    
    private void processQueryElements(List<Element> elements, String namespace, SqlQuery.QueryType queryType, 
                                     File file, Project project) {
        for (Element element : elements) {
            String id = element.attributeValue("id");
            if (id == null) continue;
            
            SqlQuery sqlQuery = new SqlQuery();
            sqlQuery.setProject(project);
            sqlQuery.setNamespace(namespace);
            sqlQuery.setQueryId(id);
            sqlQuery.setQueryType(queryType);
            sqlQuery.setQueryText(element.getTextTrim());
            sqlQuery.setFilePath(file.getAbsolutePath());
            
            sqlQueryRepository.save(sqlQuery);
        }
    }
    
    private void analyzeJavaSqlDependencies(Project project) {
        List<Method> allMethods = methodRepository.findAll();
        List<SqlQuery> allSqlQueries = sqlQueryRepository.findByProjectId(project.getId());
        
        for (Method method : allMethods) {
            // This is a simplified approach. In a real application, you would need a more robust analysis.
            String methodSource = getMethodSource(method);
            if (methodSource == null) continue;
            
            for (SqlQuery sqlQuery : allSqlQueries) {
                // Typical iBatis call patterns
                String ibatisPattern = String.format("sqlMap\\.\\w+\\([\"']%s\\.%s[\"']", 
                        Pattern.quote(sqlQuery.getNamespace()), Pattern.quote(sqlQuery.getQueryId()));
                
                Pattern pattern = Pattern.compile(ibatisPattern);
                Matcher matcher = pattern.matcher(methodSource);
                
                if (matcher.find()) {
                    Dependency dependency = new Dependency();
                    dependency.setProject(project);
                    dependency.setSourceType(Dependency.ElementType.METHOD);
                    dependency.setSourceId(method.getId());
                    dependency.setTargetType(Dependency.ElementType.SQL);
                    dependency.setTargetId(sqlQuery.getId());
                    dependency.setDependencyType("SQL_CALL");
                    dependency.setLineNumber(getLineNumber(methodSource, matcher.start()));
                    
                    dependencyRepository.save(dependency);
                }
            }
        }
    }
    
    // Simplified method to get method source - would need actual file reading in a real implementation
    private String getMethodSource(Method method) {
        // In a real implementation, you would read from the file
        // This is a placeholder
        return "public void someMethod() { sqlMap.queryForObject(\"namespace.queryId\", param); }";
    }
    
    private Integer getLineNumber(String source, int position) {
        // Count newlines up to the position
        int lines = 1;
        for (int i = 0; i < position; i++) {
            if (source.charAt(i) == '\n') {
                lines++;
            }
        }
        return lines;
    }
}

3.8 다이어그램 생성 서비스

DiagramService.java:

package cohttp://m.viw.itf.service;

import cohttp://m.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.structurizr.Workspace;
import cohttp://m.structurizr.export.plantuml.PlantUMLDiagramExporter;
import cohttp://m.structurizr.model.*;
import cohttp://m.structurizr.view.*;
import cohttp://m.viw.itf.entity.*;
import cohttp://m.viw.itf.entity.Package;
import cohttp://m.viw.itf.repository.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class DiagramService {

    private final ProjectRepository projectRepository;
    private final PackageRepository packageRepository;
    private final CodeClassRepository codeClassRepository;
    private final MethodRepository methodRepository;
    private final SqlQueryRepository sqlQueryRepository;
    private final DependencyRepository dependencyRepository;
    private final DiagramRepository diagramRepository;
    private final ObjectMapper objectMapper;
    
    @Transactional
    public Integer createComponentDiagram(Integer projectId, String name, String description) {
        Project project = projectRepository.findById(projectId)
                .orElseThrow(() -> new RuntimeException("Project not found: " + projectId));
        
        // Create a Structurizr workspace for the component diagram
        Workspace workspace = new Workspace(project.getName(), project.getDescription());
        Model model = workspace.getModel();
        
        // Create a software system to represent the project
        SoftwareSystem system = model.addSoftwareSystem(project.getName(), project.getDescription());
        
        // Find all component classes (Spring annotations)
        List<CodeClass> componentClasses = codeClassRepository.findByProjectId(projectId).stream()
                .filter(CodeClass::getIsComponent)
                .collect(Collectors.toList());
        
        // Create a container to group all components
        Container container = system.addContainer("Application", "Application Container", "Java/Spring");
        
        // Map of component ID to Structurizr Component
        Map<Integer, Component> componentMap = new HashMap<>();
        
        // Create components
        for (CodeClass componentClass : componentClasses) {
            String technology = componentClass.getComponentType() != null ? 
                    "Spring " + componentClass.getComponentType() : "Spring Component";
            
            Component component = container.addComponent(
                    componentClass.getName(),
                    componentClass.getFullName(),
                    technology);
            
            componentMap.put(componentClass.getId(), component);
        }
        
        // Add relationships between components
        for (CodeClass sourceClass : componentClasses) {
            // Find dependencies where this class is the source
            List<Dependency> dependencies = dependencyRepository.findByProjectIdAndSourceTypeAndSourceId(
                    projectId, Dependency.ElementType.CLASS, sourceClass.getId());
            
            for (Dependency dependency : dependencies) {
                if (dependency.getTargetType() == Dependency.ElementType.CLASS) {
                    Component sourceComponent = componentMap.get(sourceClass.getId());
                    Component targetComponent = componentMap.get(dependency.getTargetId());
                    
                    if (sourceComponent != null && targetComponent != null) {
                        sourceComponent.uses(targetComponent, dependency.getDependencyType());
                    }
                }
            }
        }
        
        // Create component diagram
        ViewSet viewSet = workspace.getViews();
        ComponentView componentView = viewSet.createComponentView(container, name, description);
        componentView.addAllComponents();
        
        // Export as PlantUML
        PlantUMLDiagramExporter exporter = new PlantUMLDiagramExporter();
        String plantUmlDefinition = exporter.export(componentView);
        
        // Save to diagram table
        Diagram diagram = new Diagram();
        diagram.setProject(project);
        diagram.setName(name);
        diagram.setDiagramType(Diagram.DiagramType.COMPONENT);
        diagram.setDescription(description);
        diagram.setCreationDate(LocalDateTime.now());
        diagram.setLastModified(LocalDateTime.now());
        diagram.setLayoutData(plantUmlDefinition);
        
        Diagram savedDiagram = diagramRepository.save(diagram);
        
        // Create diagram elements for each component
        for (CodeClass componentClass : componentClasses) {
            DiagramElement element = new DiagramElement();
            element.setDiagram(savedDiagram);
            element.setElementType("CLASS");
            element.setElementId(componentClass.getId());
            
            try {
                Map<String, Object> properties = new HashMap<>();
                properties.put("name", componentClass.getName());
                properties.put("type", componentClass.getComponentType());
                element.setCustomProperties(objectMapper.writeValueAsString(properties));
            } catch (JsonProcessingException e) {
                log.error("Error serializing properties", e);
            }
            
            // Positions would be set by a layout algorithm or user input
            // For now, assign random positions
            element.setXPosition(new Random().nextInt(800));
            element.setYPosition(new Random().nextInt(600));
            
            savedDiagram.getElements().add(element);
        }
        
        return savedDiagram.getId();
    }
    
    @Transactional
    public Integer createSequenceDiagram(Integer projectId, Integer methodId, String name, String description) {
        Project project = projectRepository.findById(projectId)
                .orElseThrow(() -> new RuntimeException("Project not found: " + projectId));
        
        Method rootMethod = methodRepository.findById(methodId)
                .orElseThrow(() -> new RuntimeException("Method not found: " + methodId));
        
        // Create a Structurizr workspace for the sequence diagram
        Workspace workspace = new Workspace(project.getName(), project.getDescription());
        Model model = workspace.getModel();
        
        // Create a software system
        SoftwareSystem system = model.addSoftwareSystem(project.getName(), project.getDescription());
        
        // Create a container
        Container container = system.addContainer("Application", "Application Container", "Java/Spring");
        
        // Create components for all classes involved
        Map<Integer, Component> componentMap = new HashMap<>();
        Set<Integer> processedMethodIds = new HashSet<>();
        
        // Start with the root method's class
        addComponentForClass(componentMap, container, rootMethod.getCodeClass());
        
        // Recursively process method calls
        processMethodCalls(projectId, methodId, processedMethodIds, componentMap, container);
        
        // Create dynamic view (sequence diagram)
        ViewSet viewSet = workspace.getViews();
        DynamicView dynamicView = viewSet.createDynamicView(container, name, description);
        
        // Start with the root method
        Component rootComponent = componentMap.get(rootMethod.getCodeClass().getId());
        
        // Build sequence of calls
        buildSequenceDiagram(dynamicView, rootComponent, projectId, methodId, rootMethod, new HashSet<>());
        
        // Export as PlantUML
        PlantUMLDiagramExporter exporter = new PlantUMLDiagramExporter();
        String plantUmlDefinition = exporter.export(dynamicView);
        
        // Save to diagram table
        Diagram diagram = new Diagram();
        diagram.setProject(project);
        diagram.setName(name);
        diagram.setDiagramType(Diagram.DiagramType.SEQUENCE);
        diagram.setDescription(description);
        diagram.setCreationDate(LocalDateTime.now());
        diagram.setLastModified(LocalDateTime.now());
        diagram.setLayoutData(plantUmlDefinition);
        
        return diagramRepository.save(diagram).getId();
    }
    
    private void addComponentForClass(Map<Integer, Component> componentMap, Container container, CodeClass codeClass) {
        if (!componentMap.containsKey(codeClass.getId())) {
            Component component = container.addComponent(
                    codeClass.getName(),
                    codeClass.getFullName(),
                    codeClass.getIsComponent() ? ("Spring " + codeClass.getComponentType()) : "Java Class");
            
            componentMap.put(codeClass.getId(), component);
        }
    }
    
    private void processMethodCalls(Integer projectId, Integer methodId, Set<Integer> processedMethodIds,
                                    Map<Integer, Component> componentMap, Container container) {
        if (processedMethodIds.contains(methodId)) {
            return; // Already processed
        }
        
        processedMethodIds.add(methodId);
        
        // Find method calls from this method
        List<Dependency> methodCalls = dependencyRepository.findByProjectIdAndSourceTypeAndSourceId(
                projectId, Dependency.ElementType.METHOD, methodId);
        
        for (Dependency dependency : methodCalls) {
            if (dependency.getTargetType() == Dependency.ElementType.METHOD) {
                Method targetMethod = methodRepository.findById(dependency.getTargetId())
                        .orElse(null);
                
                if (targetMethod != null) {
                    // Add component for target method's class
                    addComponentForClass(componentMap, container, targetMethod.getCodeClass());
                    
                    // Recursively process method calls
                    processMethodCalls(projectId, targetMethod.getId(), processedMethodIds, componentMap, container);
                }
            } else if (dependency.getTargetType() == Dependency.ElementType.SQL) {
                // Add a special component for database
                if (!componentMap.containsKey(-1)) { // Use a special ID for database
                    Component dbComponent = container.addComponent("Database", "Database", "MariaDB");
                    componentMap.put(-1, dbComponent);
                }
            }
        }
    }
    
    private void buildSequenceDiagram(DynamicView dynamicView, Component rootComponent,
                                     Integer projectId, Integer methodId, Method method, Set<Integer> processedCalls) {
        // Find method calls from this method
        List<Dependency> methodCalls = dependencyRepository.findByProjectIdAndSourceTypeAndSourceId(
                projectId, Dependency.ElementType.METHOD, methodId);
        
        for (Dependency dependency : methodCalls) {
            if (processedCalls.contains(dependency.getId())) {
                continue; // Avoid cycles
            }
            
            processedCalls.add(dependency.getId());
            
            if (dependency.getTargetType() == Dependency.ElementType.METHOD) {
                Method targetMethod = methodRepository.findById(dependency.getTargetId())
                        .orElse(null);
                
                if (targetMethod != null) {
                    Component targetComponent = dynamicView.getModel().getComponents().stream()
                            .filter(c -> c.getName().equals(targetMethod.getCodeClass().getName()))
                            .findFirst()
                            .orElse(null);
                    
                    if (targetComponent != null) {
                        dynamicView.add(rootComponent, targetMethod.getName(), targetComponent);
                        
                        // Recursively build sequence
                        buildSequenceDiagram(dynamicView, targetComponent, projectId, 
                                targetMethod.getId(), targetMethod, processedCalls);
                    }
                }
            } else if (dependency.getTargetType() == Dependency.ElementType.SQL) {
                SqlQuery sqlQuery = sqlQueryRepository.findById(dependency.getTargetId())
                        .orElse(null);
                
                if (sqlQuery != null) {
                    Component dbComponent = dynamicView.getModel().getComponents().stream()
                            .filter(c -> c.getName().equals("Database"))
                            .findFirst()
                            .orElse(null);
                    
                    if (dbComponent != null) {
                        dynamicView.add(rootComponent, 
                                sqlQuery.getNamespace() + "." + sqlQuery.getQueryId(), 
                                dbComponent);
                    }
                }
            }
        }
    }
    
    // Other diagram creation methods would follow a similar pattern
    @Transactional
    public Integer createClassDiagram(Integer projectId, String name, String description) {
        // Implementation similar to createComponentDiagram
        // but would include all classes, not just components
        return 0;
    }
    
    @Transactional
    public Integer createPackageDiagram(Integer projectId, String name, String description) {
        // Implementation would show package dependencies
        return 0;
    }
    
    @Transactional
    public String getDiagramContent(Integer diagramId) {
        Diagram diagram = diagramRepository.findById(diagramId)
                .orElseThrow(() -> new RuntimeException("Diagram not found: " + diagramId));
        
        return diagram.getLayoutData();
    }
    
    @Transactional
    public void updateDiagramLayout(Integer diagramId, String layoutData) {
        Diagram diagram = diagramRepository.findById(diagramId)
                .orElseThrow(() -> new RuntimeException("Diagram not found: " + diagramId));
        
        diagram.setLayoutData(layoutData);
        diagram.setLastModified(LocalDateTime.now());
        
        diagramRepository.save(diagram);
    }
}

3.9 분석 서비스

SourceAnalysisService.java:

package cohttp://m.viw.itf.service;

import cohttp://m.viw.itf.entity.Project;
import cohttp://m.viw.itf.parser.IBatisSourceParser;
import cohttp://m.viw.itf.parser.JavaSourceParser;
import cohttp://m.viw.itf.repository.ProjectRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

@Service
@RequiredArgsConstructor
@Slf4j
public class SourceAnalysisService {

    private final ProjectRepository projectRepository;
    private final JavaSourceParser javaSourceParser;
    private final IBatisSourceParser ibatisSourceParser;
    
    @Transactional
    public Integer createAndAnalyzeProject(String name, String description, String javaSourcePath, 
                                         String ibatisSourcePath, String version) {
        log.info("Creating and analyzing new project: {}", name);
        
        // Create new project
        Project project = new Project();
        project.setName(name);
        project.setDescription(description);
        project.setJavaSourcePath(javaSourcePath);
        project.setIbatisSourcePath(ibatisSourcePath);
        project.setAnalysisDate(LocalDateTime.now());
        project.setVersion(version);
        
        Project savedProject = projectRepository.save(project);
        
        // Analyze source code
        analyzeProject(savedProject.getId());
        
        return savedProject.getId();
    }
    
    @Transactional
    public void analyzeProject(Integer projectId) {
        log.info("Analyzing project: {}", projectId);
        
        Project project = projectRepository.findById(projectId)
                .orElseThrow(() -> new RuntimeException("Project not found: " + projectId));
        
        // Parse Java source files
        javaSourceParser.parseJavaFiles(project);
        
        // Parse iBatis source files
        ibatisSourceParser.parseIBatisFiles(project);
        
        // Update analysis date
        project.setAnalysisDate(LocalDateTime.now());
        projectRepository.save(project);
        
        log.info("Analysis completed for project: {}", projectId);
    }
}

3.10 REST API 컨트롤러

ProjectController.java:

package cohttp://m.viw.itf.controller;

import cohttp://m.viw.itf.dto.ProjectCreateRequest;
import cohttp://m.viw.itf.entity.Project;
import cohttp://m.viw.itf.repository.ProjectRepository;
import cohttp://m.viw.itf.service.SourceAnalysisService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

@RestController
@RequestMapping("/api/projects")
@RequiredArgsConstructor
public class ProjectController {

    private final ProjectRepository projectRepository;
    private final SourceAnalysisService sourceAnalysisService;
    
    @GetMapping
    public ResponseEntity<List<Project>> getAllProjects() {
        return ResponseEntity.ok(projectRepository.findAll());
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<Project> getProject(@PathVariable Integer id) {
        return projectRepository.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }
    
    @PostMapping
    public ResponseEntity<Project> createProject(@Valid @RequestBody ProjectCreateRequest request) {
        Integer projectId = sourceAnalysisService.createAndAnalyzeProject(
                request.getName(),
                request.getDescription(),
                request.getJavaSourcePath(),
                request.getIbatisSourcePath(),
                request.getVersion()
        );
        
        return projectRepository.findById(projectId)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }
    
    @PostMapping("/{id}/analyze")
    public ResponseEntity<Void> analyzeProject(@PathVariable Integer id) {
        sourceAnalysisService.analyzeProject(id);
        return ResponseEntity.ok().build();
    }
}

DiagramController.java:

package cohttp://m.viw.itf.controller;

import cohttp://m.viw.itf.dto.DiagramCreateRequest;
import cohttp://m.viw.itf.dto.DiagramResponse;
import cohttp://m.viw.itf.dto.DiagramUpdateRequest;
import cohttp://m.viw.itf.entity.Diagram;
import cohttp://m.viw.itf.repository.DiagramRepository;
import cohttp://m.viw.itf.service.DiagramService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/diagrams")
@RequiredArgsConstructor
public class DiagramController {

    private final DiagramRepository diagramRepository;
    private final DiagramService diagramService;
    
    @GetMapping("/project/{projectId}")
    public ResponseEntity<List<DiagramResponse>> getDiagramsByProject(@PathVariable Integer projectId) {
        List<Diagram> diagrams = diagramRepository.findByProjectId(projectId);
        
        List<DiagramResponse> response = diagrams.stream()
                .map(diagram -> new DiagramResponse(
                        diagram.getId(),
                        diagram.getName(),
                        diagram.getDiagramType().toString(),
                        diagram.getDescription(),
                        diagram.getCreationDate(),
                        diagram.getLastModified()
                ))
                .collect(Collectors.toList());
        
        return ResponseEntity.ok(response);
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<String> getDiagramContent(@PathVariable Integer id) {
        String content = diagramService.getDiagramContent(id);
        return ResponseEntity.ok(content);
    }
    
    @PostMapping("/component")
    public ResponseEntity<Integer> createComponentDiagram(@Valid @RequestBody DiagramCreateRequest request) {
        Integer diagramId = diagramService.createComponentDiagram(
                request.getProjectId(),
                request.getName(),
                request.getDescription()
        );
        
        return ResponseEntity.ok(diagramId);
    }
    
    @PostMapping("/sequence")
    public ResponseEntity<Integer> createSequenceDiagram(
            @Valid @RequestBody DiagramCreateRequest request,
            @RequestParam Integer methodId) {
        
        Integer diagramId = diagramService.createSequenceDiagram(
                request.getProjectId(),
                methodId,
                request.getName(),
                request.getDescription()
        );
        
        return ResponseEntity.ok(diagramId);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<Void> updateDiagramLayout(
            @PathVariable Integer id,
            @Valid @RequestBody DiagramUpdateRequest request) {
        
        diagramService.updateDiagramLayout(id, request.getLayoutData());
        return ResponseEntity.ok().build();
    }
}

ClassController.java:

package cohttp://m.viw.itf.controller;

import cohttp://m.viw.itf.dto.ClassResponse;
import cohttp://m.viw.itf.entity.CodeClass;
import cohttp://m.viw.itf.repository.CodeClassRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/classes")
@RequiredArgsConstructor
public class ClassController {

    private final CodeClassRepository codeClassRepository;
    
    @GetMapping("/project/{projectId}")
    public ResponseEntity<List<ClassResponse>> getClassesByProject(@PathVariable Integer projectId) {
        List<CodeClass> classes = codeClassRepository.findByProjectId(projectId);
        
        List<ClassResponse> response = classes.stream()
                .map(codeClass -> new ClassResponse(
                        codeClass.getId(),
                        codeClass.getName(),
                        codeClass.getFullName(),
                        codeClass.getPkg().getFullPath(),
                        codeClass.getClassType().toString(),
                        codeClass.getIsComponent(),
                        codeClass.getComponentType()
                ))
                .collect(Collectors.toList());
        
        return ResponseEntity.ok(response);
    }
    
    @GetMapping("/package/{packageId}")
    public ResponseEntity<List<ClassResponse>> getClassesByPackage(@PathVariable Integer packageId) {
        List<CodeClass> classes = codeClassRepository.findByPkgId(packageId);
        
        List<ClassResponse> response = classes.stream()
                .map(codeClass -> new ClassResponse(
                        codeClass.getId(),
                        codeClass.getName(),
                        codeClass.getFullName(),
                        codeClass.getPkg().getFullPath(),
                        codeClass.getClassType().toString(),
                        codeClass.getIsComponent(),
                        codeClass.getComponentType()
                ))
                .collect(Collectors.toList());
        
        return ResponseEntity.ok(response);
    }
}

3.11 DTO 클래스

ProjectCreateRequest.java:

package cohttp://m.viw.itf.dto;

import lombok.Data;

import javax.validation.constraints.NotBlank;

@Data
public class ProjectCreateRequest {
    
    @NotBlank
    private String name;
    
    private String description;
    
    @NotBlank
    private String javaSourcePath;
    
    @NotBlank
    private String ibatisSourcePath;
    
    private String version;
}

DiagramCreateRequest.java:

package cohttp://m.viw.itf.dto;

import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class DiagramCreateRequest {
    
    @NotNull
    private Integer projectId;
    
    @NotBlank
    private String name;
    
    private String description;
}

DiagramUpdateRequest.java:

package cohttp://m.viw.itf.dto;

import lombok.Data;

import javax.validation.constraints.NotBlank;

@Data
public class DiagramUpdateRequest {
    
    @NotBlank
    private String layoutData;
}

DiagramResponse.java:

package cohttp://m.viw.itf.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@AllArgsConstructor
public class DiagramResponse {
    
    private Integer id;
    private String name;
    private String type;
    private String description;
    private LocalDateTime creationDate;
    private LocalDateTime lastModified;
}

ClassResponse.java:

package cohttp://m.viw.itf.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class ClassResponse {
    
    private Integer id;
    private String name;
    private String fullName;
    private String packagePath;
    private String type;
    private Boolean isComponent;
    private String componentType;
}

4. 프론트엔드 구현

4.1 package.json

{
  "name": "itf-frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@ag-grid-community/client-side-row-model": "26.1.0",
    "@ag-grid-community/core": "26.1.0",
    "@ag-grid-community/react": "26.1.0",
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "@types/jest": "^26.0.15",
    "@types/node": "^12.0.0",
    "@types/react": "^17.0.0",
    "@types/react-dom": "^17.0.0",
    "@types/react-router-dom": "^5.1.7",
    "axios": "^0.21.1",
    "bootstrap": "^5.0.1",
    "d3": "^7.0.0",
    "file-saver": "^2.0.5",
    "html2canvas": "^1.3.2",
    "html2pdf.js": "^0.9.3",
    "jspdf": "^2.3.1",
    "mermaid": "^8.11.0",
    "mobx": "^6.3.2",
    "mobx-react-lite": "^3.2.0",
    "react": "^17.0.2",
    "react-bootstrap": "^1.6.1",
    "react-dom": "^17.0.2",
    "react-hook-form": "^7.14.1",
    "react-router-dom": "^5.2.0",
    "react-scripts": "4.0.3",
    "reactflow": "^9.5.4",
    "typescript": "^4.1.2",
    "web-vitals": "^1.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@types/d3": "^7.0.0",
    "@types/file-saver": "^2.0.3"
  }
}

4.2 TypeScript 타입 정의 (src/types/index.ts)

// Project types
export interface Project {
  id: number;
  name: string;
  description: string;
  javaSourcePath: string;
  ibatisSourcePath: string;
  analysisDate: string;
  version: string;
}

export interface ProjectCreateRequest {
  name: string;
  description?: string;
  javaSourcePath: string;
  ibatisSourcePath: string;
  version?: string;
}

// Class types
export interface CodeClass {
  id: number;
  name: string;
  fullName: string;
  packagePath: string;
  type: string;
  isComponent: boolean;
  componentType?: string;
}

// Package types
export interface Package {
  id: number;
  name: string;
  fullPath: string;
}

// Method types
export interface Method {
  id: number;
  name: string;
  signature: string;
  returnType: string;
  parameters: string;
  accessModifier: string;
  isStatic: boolean;
}

// SQL types
export interface SqlQuery {
  id: number;
  namespace: string;
  queryId: string;
  queryType: string;
  queryText: string;
  filePath: string;
}

// Dependency types
export interface Dependency {
  id: number;
  sourceType: string;
  sourceId: number;
  targetType: string;
  targetId: number;
  dependencyType: string;
  lineNumber?: number;
}

// Diagram types
export interface Diagram {
  id: number;
  name: string;
  type: string;
  description: string;
  creationDate: string;
  lastModified: string;
}

export interface DiagramCreateRequest {
  projectId: number;
  name: string;
  description?: string;
}

export interface DiagramUpdateRequest {
  layoutData: string;
}

// Element types for diagrams
export interface DiagramElement {
  id: number;
  elementType: string;
  elementId: number;
  xPosition: number;
  yPosition: number;
  width?: number;
  height?: number;
  customProperties?: any;
}

export interface DiagramRelationship {
  id: number;
  sourceElementId: number;
  targetElementId: number;
  relationshipType: string;
  label?: string;
  controlPoints?: any;
  customProperties?: any;
}

4.3 API 서비스 (src/services/api.ts)

import axios from 'axios';
import { 
  Project, 
  ProjectCreateRequest, 
  CodeClass, 
  Method, 
  SqlQuery, 
  Diagram, 
  DiagramCreateRequest, 
  DiagramUpdateRequest 
} from '../types';

const API_BASE_URL = 'http://localhost:8080/api';

// Configure axios
axios.defaults.baseURL = API_BASE_URL;
axios.defaults.headers.common['Content-Type'] = 'application/json';

// Project API
export const projectApi = {
  getAllProjects: async (): Promise<Project[]> => {
    const response = await axios.get('/projects');
    return response.data;
  },
  
  getProject: async (id: number): Promise<Project> => {
    const response = await axios.get(`/projects/${id}`);
    return response.data;
  },
  
  createProject: async (project: ProjectCreateRequest): Promise<Project> => {
    const response = await axios.post('/projects', project);
    return response.data;
  },
  
  analyzeProject: async (id: number): Promise<void> => {
    await axios.post(`/projects/${id}/analyze`);
  }
};

// Class API
export const classApi = {
  getClassesByProject: async (projectId: number): Promise<CodeClass[]> => {
    const response = await axios.get(`/classes/project/${projectId}`);
    return response.data;
  },
  
  getClassesByPackage: async (packageId: number): Promise<CodeClass[]> => {
    const response = await axios.get(`/classes/package/${packageId}`);
    return response.data;
  },
  
  getMethodsByClass: async (classId: number): Promise<Method[]> => {
    const response = await axios.get(`/methods/class/${classId}`);
    return response.data;
  }
};

// SQL API
export const sqlApi = {
  getSqlQueriesByProject: async (projectId: number): Promise<SqlQuery[]> => {
    const response = await axios.get(`/sql/project/${projectId}`);
    return response.data;
  }
};

// Diagram API
export const diagramApi = {
  getDiagramsByProject: async (projectId: number): Promise<Diagram[]> => {
    const response = await axios.get(`/diagrams/project/${projectId}`);
    return response.data;
  },
  
  getDiagramContent: async (id: number): Promise<string> => {
    const response = await axios.get(`/diagrams/${id}`);
    return response.data;
  },
  
  createComponentDiagram: async (request: DiagramCreateRequest): Promise<number> => {
    const response = await axios.post('/diagrams/component', request);
    return response.data;
  },
  
  createSequenceDiagram: async (request: DiagramCreateRequest, methodId: number): Promise<number> => {
    const response = await axios.post(`/diagrams/sequence?methodId=${methodId}`, request);
    return response.data;
  },
  
  updateDiagramLayout: async (id: number, request: DiagramUpdateRequest): Promise<void> => {
    await axios.put(`/diagrams/${id}`, request);
  }
};

4.4 MobX 상태 관리 (src/store/index.ts)

import { makeAutoObservable, runInAction } from 'mobx';
import { 
  Project, 
  CodeClass, 
  Method, 
  SqlQuery, 
  Diagram 
} from '../types';
import { projectApi, classApi, sqlApi, diagramApi } from '../services/api';

class RootStore {
  // Projects
  projects: Project[] = [];
  selectedProject: Project | null = null;
  isLoadingProjects = false;
  
  // Classes
  classes: CodeClass[] = [];
  selectedClass: CodeClass | null = null;
  isLoadingClasses = false;
  
  // Methods
  methods: Method[] = [];
  selectedMethod: Method | null = null;
  isLoadingMethods = false;
  
  // SQL Queries
  sqlQueries: SqlQuery[] = [];
  isLoadingSqlQueries = false;
  
  // Diagrams
  diagrams: Diagram[] = [];
  selectedDiagram: Diagram | null = null;
  diagramContent: string = '';
  isLoadingDiagrams = false;
  
  constructor() {
    makeAutoObservable(this);
  }
  
  // Project actions
  async loadProjects() {
    this.isLoadingProjects = true;
    try {
      const projects = await projectApi.getAllProjects();
      runInAction(() => {
        this.projects = projects;
        this.isLoadingProjects = false;
      });
    } catch (error) {
      runInAction(() => {
        this.isLoadingProjects = false;
        console.error('Failed to load projects', error);
      });
    }
  }
  
  async loadProject(id: number) {
    this.isLoadingProjects = true;
    try {
      const project = await projectApi.getProject(id);
      runInAction(() => {
        this.selectedProject = project;
        this.isLoadingProjects = false;
      });
      return project;
    } catch (error) {
      runInAction(() => {
        this.isLoadingProjects = false;
        console.error(`Failed to load project id=${id}`, error);
      });
      return null;
    }
  }
  
  async createProject(project: Project) {
    try {
      const createdProject = await projectApi.createProject(project);
      runInAction(() => {
        this.projects.push(createdProject);
        this.selectedProject = createdProject;
      });
      return createdProject;
    } catch (error) {
      console.error('Failed to create project', error);
      return null;
    }
  }
  
  // Class actions
  async loadClassesByProject(projectId: number) {
    this.isLoadingClasses = true;
    try {
      const classes = await classApi.getClassesByProject(projectId);
      runInAction(() => {
        this.classes = classes;
        this.isLoadingClasses = false;
      });
    } catch (error) {
      runInAction(() => {
        this.isLoadingClasses = false;
        console.error(`Failed to load classes for project id=${projectId}`, error);
      });
    }
  }
  
  setSelectedClass(cls: CodeClass | null) {
    this.selectedClass = cls;
  }
  
  // Method actions
  async loadMethodsByClass(classId: number) {
    this.isLoadingMethods = true;
    try {
      const methods = await classApi.getMethodsByClass(classId);
      runInAction(() => {
        this.methods = methods;
        this.isLoadingMethods = false;
      });
    } catch (error) {
      runInAction(() => {
        this.isLoadingMethods = false;
        console.error(`Failed to load methods for class id=${classId}`, error);
      });
    }
  }
  
  setSelectedMethod(method: Method | null) {
    this.selectedMethod = method;
  }
  
  // SQL actions
  async loadSqlQueriesByProject(projectId: number) {
    this.isLoadingSqlQueries = true;
    try {
      const queries = await sqlApi.getSqlQueriesByProject(projectId);
      runInAction(() => {
        this.sqlQueries = queries;
        this.isLoadingSqlQueries = false;
      });
    } catch (error) {
      runInAction(() => {
        this.isLoadingSqlQueries = false;
        console.error(`Failed to load SQL queries for project id=${projectId}`, error);
      });
    }
  }
  
  // Diagram actions
  async loadDiagramsByProject(projectId: number) {
    this.isLoadingDiagrams = true;
    try {
      const diagrams = await diagramApi.getDiagramsByProject(projectId);
      runInAction(() => {
        this.diagrams = diagrams;
        this.isLoadingDiagrams = false;
      });
    } catch (error) {
      runInAction(() => {
        this.isLoadingDiagrams = false;
        console.error(`Failed to load diagrams for project id=${projectId}`, error);
      });
    }
  }
  
  async loadDiagramContent(id: number) {
    this.isLoadingDiagrams = true;
    try {
      const content = await diagramApi.getDiagramContent(id);
      runInAction(() => {
        this.diagramContent = content;
        this.isLoadingDiagrams = false;
      });
    } catch (error) {
      runInAction(() => {
        this.isLoadingDiagrams = false;
        console.error(`Failed to load diagram content for id=${id}`, error);
      });
    }
  }
  
  setSelectedDiagram(diagram: Diagram | null) {
    this.selectedDiagram = diagram;
  }
  
  async createComponentDiagram(projectId: number, name: string, description?: string) {
    try {
      const diagramId = await diagramApi.createComponentDiagram({
        projectId,
        name,
        description
      });
      await this.loadDiagramsByProject(projectId);
      return diagramId;
    } catch (error) {
      console.error('Failed to create component diagram', error);
      return null;
    }
  }
  
  async createSequenceDiagram(projectId: number, methodId: number, name: string, description?: string) {
    try {
      const diagramId = await diagramApi.createSequenceDiagram({
        projectId,
        name,
        description
      }, methodId);
      await this.loadDiagramsByProject(projectId);
      return diagramId;
    } catch (error) {
      console.error('Failed to create sequence diagram', error);
      return null;
    }
  }
  
  async updateDiagramLayout(id: number, layoutData: string) {
    try {
      await diagramApi.updateDiagramLayout(id, { layoutData });
      // Refresh content
      await this.loadDiagramContent(id);
    } catch (error) {
      console.error(`Failed to update diagram layout for id=${id}`, error);
    }
  }
}

export const rootStore = new RootStore();

4.5 React 컴포넌트

App.tsx

import React from 'react';
import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
import { Container } from 'react-bootstrap';
import Navigation from './components/Navigation';
import HomePage from './pages/HomePage';
import ProjectListPage from './pages/ProjectListPage';
import ProjectDetailPage from './pages/ProjectDetailPage';
import DiagramListPage from './pages/DiagramListPage';
import DiagramDetailPage from './pages/DiagramDetailPage';
import CreateDiagramPage from './pages/CreateDiagramPage';
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';

const App: React.FC = () => {
  return (
    <Router>
      <Navigation />
      <Container fluid className="app-container">
        <Switch>
          <Route exact path="/" component={HomePage} />
          <Route exact path="/projects" component={ProjectListPage} />
          <Route exact path="/projects/:projectId" component={ProjectDetailPage} />
          <Route exact path="/projects/:projectId/diagrams" component={DiagramListPage} />
          <Route exact path="/diagrams/:diagramId" component={DiagramDetailPage} />
          <Route exact path="/projects/:projectId/create-diagram" component={CreateDiagramPage} />
          <Redirect to="/" />
        </Switch>
      </Container>
    </Router>
  );
};

export default App;

src/components/Navigation.tsx

import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Navbar, Nav, Container } from 'react-bootstrap';

const Navigation: React.FC = () => {
  const location = useLocation();
  
  return (
    <Navbar bg="dark" variant="dark" expand="lg">
      <Container>
        <Navbar.Brand as={Link} to="/">Code Visualizer</Navbar.Brand>
        <Navbar.Toggle aria-controls="basic-navbar-nav" />
        <Navbar.Collapse id="basic-navbar-nav">
          <Nav className="me-auto" activeKey={location.pathname}>
            <Nav.Link as={Link} to="/">Home</Nav.Link>
            <Nav.Link as={Link} to="/projects">Projects</Nav.Link>
          </Nav>
        </Navbar.Collapse>
      </Container>
    </Navbar>
  );
};

export default Navigation;

src/pages/HomePage.tsx

import React from 'react';
import { Link } from 'react-router-dom';
import { Container, Row, Col, Card, Button } from 'react-bootstrap';

const HomePage: React.FC = () => {
  return (
    <Container className="py-5">
      <Row className="mb-4">
        <Col>
          <h1>Code Visualization Tool</h1>
          <p className="lead">
            Analyze Java and iBatis source code to generate interactive diagrams that 
            help you understand complex systems.
          </p>
        </Col>
      </Row>
      
      <Row>
        <Col md={4} className="mb-4">
          <Card>
            <Card.Body>
              <Card.Title>Source Code Analysis</Card.Title>
              <Card.Text>
                Analyze Java and iBatis source code to extract class structures, 
                methods, and dependencies.
              </Card.Text>
              <Button as={Link} to="/projects" variant="primary">
                View Projects
              </Button>
            </Card.Body>
          </Card>
        </Col>
        
        <Col md={4} className="mb-4">
          <Card>
            <Card.Body>
              <Card.Title>Interactive Diagrams</Card.Title>
              <Card.Text>
                Generate sequence diagrams, class diagrams, component diagrams,
                and more from your code.
              </Card.Text>
              <Button as={Link} to="/projects" variant="primary">
                Create Diagrams
              </Button>
            </Card.Body>
          </Card>
        </Col>
        
        <Col md={4} className="mb-4">
          <Card>
            <Card.Body>
              <Card.Title>Export & Share</Card.Title>
              <Card.Text>
                Export diagrams as PDF files for documentation and sharing
                with your team.
              </Card.Text>
              <Button as={Link} to="/projects" variant="primary">
                View Diagrams
              </Button>
            </Card.Body>
          </Card>
        </Col>
      </Row>
    </Container>
  );
};

export default HomePage;

src/pages/ProjectListPage.tsx

import React, { useEffect, useState } from 'react';
import { observer } from 'mobx-react-lite';
import { Link } from 'react-router-dom';
import { Container, Row, Col, Card, Button, Table, Spinner, Modal, Form } from 'react-bootstrap';
import { useForm } from 'react-hook-form';
import { rootStore } from '../store';
import { ProjectCreateRequest } from '../types';

const ProjectListPage: React.FC = observer(() => {
  const { projects, isLoadingProjects, loadProjects, createProject } = rootStore;
  const [showModal, setShowModal] = useState(false);
  const { register, handleSubmit, formState: { errors }, reset } = useForm<ProjectCreateRequest>();
  
  useEffect(() => {
    loadProjects();
  }, []);
  
  const handleCreateProject = async (data: ProjectCreateRequest) => {
    await createProject(data as any);
    setShowModal(false);
    reset();
  };
  
  return (
    <Container className="py-4">
      <Row className="mb-4">
        <Col>
          <h1>Projects</h1>
        </Col>
        <Col xs="auto">
          <Button variant="primary" onClick={() => setShowModal(true)}>
            Create New Project
          </Button>
        </Col>
      </Row>
      
      {isLoadingProjects ? (
        <div className="text-center py-5">
          <Spinner animation="border" role="status">
            <span className="visually-hidden">Loading...</span>
          </Spinner>
        </div>
      ) : projects.length === 0 ? (
        <Card className="text-center py-5">
          <Card.Body>
            <Card.Title>No Projects Found</Card.Title>
            <Card.Text>
              Create a new project to get started with code analysis and visualization.
            </Card.Text>
            <Button variant="primary" onClick={() => setShowModal(true)}>
              Create New Project
            </Button>
          </Card.Body>
        </Card>
      ) : (
        <Table striped bordered hover>
          <thead>
            <tr>
              <th>Name</th>
              <th>Description</th>
              <th>Analysis Date</th>
              <th>Version</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody>
            {projects.map(project => (
              <tr key={project.id}>
                <td>{project.name}</td>
                <td>{project.description}</td>
                <td>{new Date(project.analysisDate).toLocaleString()}</td>
                <td>{project.version}</td>
                <td>
                  <Button 
                    variant="outline-primary" 
                    size="sm"
                    as={Link} 
                    to={`/projects/${project.id}`}
                    className="me-2"
                  >
                    Details
                  </Button>
                  <Button 
                    variant="outline-success" 
                    size="sm"
                    as={Link} 
                    to={`/projects/${project.id}/diagrams`}
                  >
                    Diagrams
                  </Button>
                </td>
              </tr>
            ))}
          </tbody>
        </Table>
      )}
      
      {/* Create Project Modal */}
      <Modal show={showModal} onHide={() => setShowModal(false)}>
        <Modal.Header closeButton>
          <Modal.Title>Create New Project</Modal.Title>
        </Modal.Header>
        <Form onSubmit={handleSubmit(handleCreateProject)}>
          <Modal.Body>
            <Form.Group className="mb-3">
              <Form.Label>Project Name</Form.Label>
              <Form.Control 
                type="text" 
                {...register('name', { required: 'Project name is required' })}
                isInvalid={!!errors.name}
              />
              <Form.Control.Feedback type="invalid">
                {errors.name?.message}
              </Form.Control.Feedback>
            </Form.Group>
            
            <Form.Group className="mb-3">
              <Form.Label>Description</Form.Label>
              <Form.Control 
                as="textarea" 
                rows={3} 
                {...register('description')}
              />
            </Form.Group>
            
            <Form.Group className="mb-3">
              <Form.Label>Java Source Path</Form.Label>
              <Form.Control 
                type="text" 
                {...register('javaSourcePath', { required: 'Java source path is required' })}
                isInvalid={!!errors.javaSourcePath}
                placeholder="C:\work_back\viw_ep_edi\src\main\java\com\viw\esp\online"
              />
              <Form.Control.Feedback type="invalid">
                {errors.javaSourcePath?.message}
              </Form.Control.Feedback>
            </Form.Group>
            
            <Form.Group className="mb-3">
              <Form.Label>iBatis Source Path</Form.Label>
              <Form.Control 
                type="text" 
                {...register('ibatisSourcePath', { required: 'iBatis source path is required' })}
                isInvalid={!!errors.ibatisSourcePath}
                placeholder="C:\work_back\viw_erp_wms\src\main\resources\sql"
              />
              <Form.Control.Feedback type="invalid">
                {errors.ibatisSourcePath?.message}
              </Form.Control.Feedback>
            </Form.Group>
            
            <Form.Group className="mb-3">
              <Form.Label>Version</Form.Label>
              <Form.Control 
                type="text" 
                {...register('version')}
              />
            </Form.Group>
          </Modal.Body>
          <Modal.Footer>
            <Button variant="secondary" onClick={() => setShowModal(false)}>
              Cancel
            </Button>
            <Button variant="primary" type="submit">
              Create Project
            </Button>
          </Modal.Footer>
        </Form>
      </Modal>
    </Container>
  );
});

export default ProjectListPage;

src/pages/ProjectDetailPage.tsx

import React, { useEffect, useState } from 'react';
import { observer } from 'mobx-react-lite';
import { useParams, Link } from 'react-router-dom';
import { Container, Row, Col, Card, Button, Tabs, Tab, ListGroup, Badge, Spinner } from 'react-bootstrap';
import { rootStore } from '../store';
import { CodeClass, Method, SqlQuery } from '../types';

interface ParamTypes {
  projectId: string;
}

const ProjectDetailPage: React.FC = observer(() => {
  const { projectId } = useParams<ParamTypes>();
  const { 
    selectedProject, 
    classes, 
    methods, 
    sqlQueries,
    isLoadingProject, 
    isLoadingClasses, 
    isLoadingMethods, 
    isLoadingSqlQueries,
    loadProject,
    loadClassesByProject,
    loadMethodsByClass,
    loadSqlQueriesByProject,
    setSelectedClass
  } = rootStore;
  
  const [selectedClassId, setSelectedClassId] = useState<number | null>(null);
  
  useEffect(() => {
    const id = parseInt(projectId);
    loadProject(id);
    loadClassesByProject(id);
    loadSqlQueriesByProject(id);
  }, [projectId]);
  
  const handleClassClick = (cls: CodeClass) => {
    setSelectedClassId(cls.id);
    setSelectedClass(cls);
    loadMethodsByClass(cls.id);
  };
  
  if (isLoadingProject) {
    return (
      <div className="text-center py-5">
        <Spinner animation="border" role="status">
          <span className="visually-hidden">Loading project...</span>
        </Spinner>
      </div>
    );
  }
  
  if (!selectedProject) {
    return (
      <Container className="py-4">
        <Card className="text-center py-5">
          <Card.Body>
            <Card.Title>Project Not Found</Card.Title>
            <Button as={Link} to="/projects" variant="primary">
              Back to Projects
            </Button>
          </Card.Body>
        </Card>
      </Container>
    );
  }
  
  return (
    <Container fluid className="py-4">
      <Row className="mb-4">
        <Col>
          <h1>{selectedProject.name}</h1>
          <p>{selectedProject.description}</p>
        </Col>
        <Col xs="auto">
          <Button 
            variant="primary" 
            as={Link} 
            to={`/projects/${projectId}/create-diagram`}
            className="me-2"
          >
            Create Diagram
          </Button>
          <Button 
            variant="outline-primary" 
            as={Link} 
            to={`/projects/${projectId}/diagrams`}
          >
            View Diagrams
          </Button>
        </Col>
      </Row>
      
      <Row>
        <Col>
          <Tabs defaultActiveKey="classes" className="mb-3">
            <Tab eventKey="classes" title="Classes">
              <Row>
                <Col md={4}>
                  <Card>
                    <Card.Header>
                      <strong>Classes</strong>
                      {isLoadingClasses && (
                        <Spinner animation="border" size="sm" className="ms-2" />
                      )}
                    </Card.Header>
                    <ListGroup variant="flush" style={{ maxHeight: '600px', overflowY: 'auto' }}>
                      {classes.map(cls => (
                        <ListGroup.Item 
                          key={cls.id} 
                          action 
                          active={selectedClassId === cls.id}
                          onClick={() => handleClassClick(cls)}
                        >
                          <div className="d-flex justify-content-between align-items-center">
                            <div>
                              <div className="fw-bold">{cls.name}</div>
                              <small className="text-muted">{cls.packagePath}</small>
                            </div>
                            <div>
                              <Badge bg={cls.type === 'CLASS' ? 'primary' : 'info'}>
                                {cls.type}
                              </Badge>
                              {cls.isComponent && (
                                <Badge bg="success" className="ms-1">
                                  {cls.componentType}
                                </Badge>
                              )}
                            </div>
                          </div>
                        </ListGroup.Item>
                      ))}
                    </ListGroup>
                  </Card>
                </Col>
                <Col md={8}>
                  <Card>
                    <Card.Header>
                      <strong>Methods</strong>
                      {isLoadingMethods && (
                        <Spinner animation="border" size="sm" className="ms-2" />
                      )}
                    </Card.Header>
                    <ListGroup variant="flush" style={{ maxHeight: '600px', overflowY: 'auto' }}>
                      {selectedClassId === null ? (
                        <ListGroup.Item>Select a class to view its methods</ListGroup.Item>
                      ) : methods.length === 0 ? (
                        <ListGroup.Item>No methods found</ListGroup.Item>
                      ) : (
                        methods.map(method => (
                          <ListGroup.Item key={method.id}>
                            <div className="d-flex justify-content-between align-items-center">
                              <div>
                                <div className="fw-bold">{method.name}</div>
                                <small className="text-muted">{method.signature}</small>
                              </div>
                              <div>
                                <Button 
                                  variant="outline-primary" 
                                  size="sm"
                                  as={Link}
                                  to={`/projects/${projectId}/create-diagram?type=sequence&methodId=${method.id}`}
                                >
                                  Sequence Diagram
                                </Button>
                              </div>
                            </div>
                          </ListGroup.Item>
                        ))
                      )}
                    </ListGroup>
                  </Card>
                </Col>
              </Row>
            </Tab>
            <Tab eventKey="sql" title="SQL Queries">
              <Card>
                <Card.Header>
                  <strong>SQL Queries</strong>
                  {isLoadingSqlQueries && (
                    <Spinner animation="border" size="sm" className="ms-2" />
                  )}
                </Card.Header>
                <ListGroup variant="flush" style={{ maxHeight: '600px', overflowY: 'auto' }}>
                  {sqlQueries.length === 0 ? (
                    <ListGroup.Item>No SQL queries found</ListGroup.Item>
                  ) : (
                    sqlQueries.map(query => (
                      <ListGroup.Item key={query.id}>
                        <div>
                          <div className="d-flex justify-content-between">
                            <div className="fw-bold">
                              {query.namespace}.{query.queryId}
                            </div>
                            <Badge bg={
                              query.queryType === 'SELECT' ? 'success' :
                              query.queryType === 'INSERT' ? 'primary' :
                              query.queryType === 'UPDATE' ? 'warning' :
                              query.queryType === 'DELETE' ? 'danger' : 'secondary'
                            }>
                              {query.queryType}
                            </Badge>
                          </div>
                          <small className="text-muted d-block mt-1">{query.filePath}</small>
                          <pre className="mt-2 p-2 bg-light rounded">
                            <code>{query.queryText.substring(0, 200)}...</code>
                          </pre>
                        </div>
                      </ListGroup.Item>
                    ))
                  )}
                </ListGroup>
              </Card>
            </Tab>
          </Tabs>
        </Col>
      </Row>
    </Container>
  );
});

export default ProjectDetailPage;

src/pages/DiagramListPage.tsx

import React, { useEffect } from 'react';
import { observer } from 'mobx-react-lite';
import { useParams, Link } from 'react-router-dom';
import { Container, Row, Col, Card, Button, Spinner, Table } from 'react-bootstrap';
import { rootStore } from '../store';

interface ParamTypes {
  projectId: string;
}

const DiagramListPage: React.FC = observer(() => {
  const { projectId } = useParams<ParamTypes>();
  const { 
    selectedProject, 
    diagrams, 
    isLoadingProject, 
    isLoadingDiagrams,
    loadProject,
    loadDiagramsByProject
  } = rootStore;
  
  useEffect(() => {
    const id = parseInt(projectId);
    loadProject(id);
    loadDiagramsByProject(id);
  }, [projectId]);
  
  if (isLoadingProject || isLoadingDiagrams) {
    return (
      <div className="text-center py-5">
        <Spinner animation="border" role="status">
          <span className="visually-hidden">Loading...</span>
        </Spinner>
      </div>
    );
  }
  
  if (!selectedProject) {
    return (
      <Container className="py-4">
        <Card className="text-center py-5">
          <Card.Body>
            <Card.Title>Project Not Found</Card.Title>
            <Button as={Link} to="/projects" variant="primary">
              Back to Projects
            </Button>
          </Card.Body>
        </Card>
      </Container>
    );
  }
  
  return (
    <Container className="py-4">
      <Row className="mb-4">
        <Col>
          <h1>Diagrams for {selectedProject.name}</h1>
        </Col>
        <Col xs="auto">
          <Button 
            variant="primary" 
            as={Link} 
            to={`/projects/${projectId}/create-diagram`}
          >
            Create New Diagram
          </Button>
        </Col>
      </Row>
      
      {diagrams.length === 0 ? (
        <Card className="text-center py-5">
          <Card.Body>
            <Card.Title>No Diagrams Found</Card.Title>
            <Card.Text>
              Create a new diagram to visualize the structure and behavior of your code.
            </Card.Text>
            <Button 
              variant="primary" 
              as={Link} 
              to={`/projects/${projectId}/create-diagram`}
            >
              Create New Diagram
            </Button>
          </Card.Body>
        </Card>
      ) : (
        <Table striped bordered hover>
          <thead>
            <tr>
              <th>Name</th>
              <th>Type</th>
              <th>Description</th>
              <th>Last Modified</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody>
            {diagrams.map(diagram => (
              <tr key={diagram.id}>
                <td>{diagram.name}</td>
                <td>{diagram.type}</td>
                <td>{diagram.description}</td>
                <td>{new Date(diagram.lastModified).toLocaleString()}</td>
                <td>
                  <Button 
                    variant="outline-primary" 
                    size="sm"
                    as={Link} 
                    to={`/diagrams/${diagram.id}`}
                  >
                    View
                  </Button>
                </td>
              </tr>
            ))}
          </tbody>
        </Table>
      )}
      
      <div className="mt-4">
        <Button variant="secondary" as={Link} to={`/projects/${projectId}`}>
          Back to Project
        </Button>
      </div>
    </Container>
  );
});

export default DiagramListPage;

src/pages/DiagramDetailPage.tsx

import React, { useEffect, useRef } from 'react';
import { observer } from 'mobx-react-lite';
import { useParams, Link } from 'react-router-dom';
import { Container, Row, Col, Card, Button, Spinner } from 'react-bootstrap';
import mermaid from 'mermaid';
import { rootStore } from '../store';
import { exportToPdf } from '../utils/export';

interface ParamTypes {
  diagramId: string;
}

const DiagramDetailPage: React.FC = observer(() => {
  const { diagramId } = useParams<ParamTypes>();
  const { 
    selectedDiagram,
    diagramContent,
    isLoadingDiagrams,
    setSelectedDiagram,
    loadDiagramContent
  } = rootStore;
  
  const diagramRef = useRef<HTMLDivElement>(null);
  
  useEffect(() => {
    const id = parseInt(diagramId);
    
    // Configure mermaid
    mermaid.initialize({
      startOnLoad: true,
      theme: 'default',
      securityLevel: 'loose'
    });
    
    loadDiagramContent(id);
    
    // Clean up
    return () => {
      setSelectedDiagram(null);
    };
  }, [diagramId]);
  
  useEffect(() => {
    if (diagramContent && diagramRef.current) {
      // Clear previous content
      diagramRef.current.innerHTML = '';
      
      // Set content
      diagramRef.current.innerHTML = diagramContent;
      
      // Initialize mermaid diagrams
      mermaid.init(undefined, diagramRef.current);
    }
  }, [diagramContent]);
  
  const handleExportPdf = () => {
    if (diagramRef.current && selectedDiagram) {
      exportToPdf(diagramRef.current, selectedDiagram.name);
    }
  };
  
  if (isLoadingDiagrams) {
    return (
      <div className="text-center py-5">
        <Spinner animation="border" role="status">
          <span className="visually-hidden">Loading diagram...</span>
        </Spinner>
      </div>
    );
  }
  
  return (
    <Container fluid className="py-4">
      <Row className="mb-3">
        <Col>
          <h1>{selectedDiagram?.name}</h1>
          <p>{selectedDiagram?.description}</p>
        </Col>
        <Col xs="auto">
          <Button 
            variant="primary" 
            onClick={handleExportPdf}
          >
            Export to PDF
          </Button>
        </Col>
      </Row>
      
      <Card>
        <Card.Body>
          <div ref={diagramRef} className="diagram-container">
            {/* Diagram content will be rendered here */}
          </div>
        </Card.Body>
      </Card>
    </Container>
  );
});

export default DiagramDetailPage;

src/pages/CreateDiagramPage.tsx

import React, { useEffect, useState } from 'react';
import { observer } from 'mobx-react-lite';
import { useParams, useLocation, useHistory } from 'react-router-dom';
import { Container, Row, Col, Card, Button, Form, Spinner } from 'react-bootstrap';
import { useForm } from 'react-hook-form';
import { rootStore } from '../store';
import { DiagramCreateRequest } from '../types';

interface ParamTypes {
  projectId: string;
}

const CreateDiagram

클래스 다이어그램(Class Diagram)
객체 다이어그램(Object  Diagram)
복합 구조 다이어그램(Complex Structure Diagram)
배포 다이어그램(Deployment Diagram)
컴포넌트 다이어그램(Component Diagram)
패키지 다이어그램(Package Diagram)
프로필 다이어그램
활동 다이어그램(Activity Diagram)
상태 머신 다이어그램(State Machine Diagram)
유스케이스 다이어그램(Use Case Diagram)
상호작용 다이어그램(Interaction Diagram)
커뮤니케이션 다이어그램(Communication Diagram)
시퀀스 다이어그램(Sequence Diagram)
타이밍 다이어그램플로우차트 (Flowchart)
플로우차트 특징플로우차트 기호플로우차트
프로그램 순서도
UML, 플로우차트
마인드맵 (Mind Map)

code-analyzer/
├── backend/ # 백엔드 프로젝트
│ ├── src/main/java/com/codemap/
│ │ ├── CodeAnalyzerApplication.java
│ │ ├── batch/ # 배치 작업 관련 클래스
│ │ ├── controller/ # REST API 컨트롤러
│ │ ├── dto/ # 데이터 전송 객체
│ │ ├── entity/ # JPA 엔티티 클래스
│ │ ├── repository/ # 데이터 액세스 계층
│ │ └── service/ # 비즈니스 로직 서비스
│ ├── src/main/resources/
│ │ ├── application.properties # 애플리케이션 설정
│ │ └── schema.sql # 데이터베이스 스키마 스크립트
│ └── pom.xml # Maven 빌드 설정
└── frontend/ # 프론트엔드 프로젝트

├── public/                   # 정적 파일
├── src/
│   ├── components/           # React 컴포넌트
│   ├── services/             # API 서비스
│   ├── types/                # TypeScript 타입 정의
│   ├── App.tsx               # 메인 앱 컴포넌트
│   └── index.tsx             # 진입점
├── package.json              # NPM 패키지 설정
└── tsconfig.json             # TypeScript 설정