diff --git "a/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/ANT_Style_Pattern.md" "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/ANT_Style_Pattern.md"
new file mode 100644
index 0000000..8eca5fe
--- /dev/null
+++ "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/ANT_Style_Pattern.md"
@@ -0,0 +1,148 @@
+# Ant-style Pattern
+
+## ๐ก ํต์ฌ ์์ฝ ๐ก
+
+> - **ํ ์ค ์ ์** : ํ์ผ ๊ฒฝ๋ก(Path)๋ URL์ ๋งค์นญํ๊ธฐ ์ํด Apache Ant ๋น๋ ๋๊ตฌ์์ ๊ณ ์๋ ์ง๊ด์ ์ธ **์์ผ๋์นด๋ ํจํด** ์
๋๋ค.
+>
+> - **ํต์ฌ ํค์๋** : `์์ผ๋์นด๋`, `๊ฒฝ๋ก ๋งค์นญ(Path Matching)`, `Double Asterisk(**)`, `Glob Pattern`
+>
+> - **์ ์ค์ํ๊ฐ?** : ์ ๊ทํํ์๋ณด๋ค ๋ฌธ๋ฒ์ด ํจ์ฌ ๋จ์ํ์ฌ ๋๋ ํฐ๋ฆฌ ๊ตฌ์กฐ ํ์์ ์ต์ ํ๋์ด ์์ต๋๋ค. ํนํ **Spring Framework** ์ URL ์ค์ , ํ์ผ ๊ฒ์ ๋ฑ์์ ํ์ค์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
+
+## 1. ๊ฐ๋
+
+**Ant-style Pattern** ์ ํ์ผ ์์คํ
์ ๊ฒฝ๋ก(Path)๋ URL์ ์ฝ๊ณ ๊ฐ๋จํ๊ฒ ํํํ๊ธฐ ์ํด ๋ง๋ค์ด์ง ๊ท์น์
๋๋ค.
+
+๋ณต์กํ ๋ฌธ์์ด ์ฒ๋ฆฌ๋ฅผ ์ํ '์ ๊ทํํ์'๊ณผ ๋ฌ๋ฆฌ, **'๋๋ ํฐ๋ฆฌ(ํด๋)์ ํ์ผ์ ๊ณ์ธต ๊ตฌ์กฐ'**๋ฅผ ๋งค์นญํ๋ ๋ฐ ํนํ๋์ด ์์ต๋๋ค. ์ฐ๋ฆฌ ์์
์์๋ Spring Framework์์ ๋ฆฌ์์ค ์์น๋ URL ํจํด(`@RequestMapping`)์ ์ ์ํ ๋ ์ฃผ๋ก ์ฌ์ฉํ์ต๋๋ค.
+
+-----
+
+## 2. ์ ํ์ํ๊ฐ?
+
+### ์ ๊ทํํ์์ ๋ณต์ก์ฑ ํด์
+
+ - ํ์ผ ๊ฒฝ๋ก๋ URL์ `/` (์ฌ๋์)๋ก ๊ตฌ๋ถ๋ ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๋๋ค.
+ - ์ด๋ฅผ ์ ๊ทํํ์์ผ๋ก ํํํ๋ ค๋ฉด ์ด์ค์ผ์ดํ ์ฒ๋ฆฌ(`\/`)์ ๋ณต์กํ ์๋์ ์กฐํฉ์ด ํ์ํด ๊ฐ๋
์ฑ์ด ๋จ์ด์ง๋๋ค.
+ - Ant ํจํด์ `*`, `**`, `?` ์ธ ๊ฐ์ง๋ง์ผ๋ก ์ง๊ด์ ์ธ ๊ฒฝ๋ก ํํ์ด ๊ฐ๋ฅํฉ๋๋ค.
+
+### ๋๋ ํฐ๋ฆฌ ์ฌ๊ท ํ์ (Recursive Search)
+
+ - ๊ฐ์ฅ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ธ `**`๋ฅผ ํตํด ํ์ ๋๋ ํฐ๋ฆฌ์ ๊น์ด(Depth)์ ์๊ด์์ด ๋ชจ๋ ๊ฒฝ๋ก๋ฅผ ํฌํจํ ์ ์์ต๋๋ค.
+ - ์ด๋ ์น ์ ํ๋ฆฌ์ผ์ด์
์์ "ํน์ ๊ฒฝ๋ก ํ์์ ๋ชจ๋ ํ์ด์ง์ ๋ณด์ ํํฐ๋ฅผ ์ ์ฉ"ํ๋ ๋ฑ์ ์๋๋ฆฌ์ค์ ํ์์ ์
๋๋ค.
+
+-----
+
+## 3. ์ปดํจํฐ ๊ณผํ ๋ด์์ Ant ํจํด์ ์์น
+
+### Glob Pattern์ ์ผ์ข
+
+Ant ํจํด์ ์ปดํจํฐ ๊ณผํ์์ **Glob Pattern**์ ํ์ฅ๋ ํํ์
๋๋ค.
+
+ - **Glob Pattern**: ์ ๋์ค ์
ธ(Shell)์์ ํ์ผ ์ด๋ฆ ํ์ฅ์ ์ํด ์ฌ์ฉํ๋ ํจํด (์: `ls *.txt`)
+ - **Ant Pattern**: Glob ํจํด์ **๋๋ ํฐ๋ฆฌ ์ฌ๊ท ํ์(`**`)** ๊ฐ๋
์ ์ถ๊ฐํ์ฌ ํ์ฅํ ๊ฒ
+
+### ์ ๊ทํํ์๊ณผ์ ๊ด๊ณ
+
+Ant ํจํด์ ์ ๊ทํํ์์ **๋ถ๋ถ์งํฉ(Subset)**์ฒ๋ผ ๋์ํ์ง๋ง, ๋ด๋ถ์ ์ผ๋ก๋ ์ ๊ทํํ์์ผ๋ก ๋ณํ๋์ด ์ฒ๋ฆฌ๋๊ฑฐ๋ ๋ณ๋์ ํ์(Parser)๋ฅผ ํตํด ํด์๋ฉ๋๋ค.
+
+| ํน์ฑ | Ant Pattern | ์ ๊ทํํ์ (Regex) |
+| :--- | :--- | :--- |
+| **์ฃผ ๋ชฉ์ ** | ๊ฒฝ๋ก(Path), ํ์ผ, URL ๋งค์นญ | ๋ฒ์ฉ ๋ฌธ์์ด ๊ฒ์ ๋ฐ ์กฐ์ |
+| **๋ณต์ก๋** | ๋งค์ฐ ๋ฎ์ (๋จ์) | ๋์ (๊ฐ๋ ฅํจ) |
+| **์ฃผ์ ์ฌ๋ณผ** | `?`, `*`, `**` | `.`, `*`, `+`, `^`, `$`, `[]` ๋ฑ ๋ค์ |
+| **๊ฒฝ๊ณ ๊ธฐ์ค** | `/` (๋๋ ํฐ๋ฆฌ ๊ตฌ๋ถ์) ๊ธฐ์ค | ๋ฌธ์ ๋จ์ ๊ธฐ์ค |
+
+-----
+
+## 4. Ant ํจํด์ ์ฃผ์ ๊ตฌ์ฑ ์์ (Syntax)
+
+Ant ํจํด์ ๋ค์ 3๊ฐ์ง ํน์ ๋ฌธ์๋ฅผ ์กฐํฉํ์ฌ ์ฌ์ฉํฉ๋๋ค.
+
+### 4.1 `?` (๋ฌผ์ํ)
+
+**ํ ๊ธ์**์ ๋งค์นญ๋ฉ๋๋ค. (๋๋ ํฐ๋ฆฌ ๊ตฌ๋ถ์ `/` ์ ์ธ)
+
+```java
+// ์์: t?st.txt
+"test.txt" (O) // ? = e
+"tast.txt" (O) // ? = a
+"tst.txt" (X) // ๋ฌธ์๊ฐ ์์ด์ผ ํจ
+"teest.txt" (X) // ํ ๊ธ์๋ง ๊ฐ๋ฅ
+```
+
+### 4.2 `*` (๋ณํ)
+
+**0๊ฐ ์ด์์ ๋ฌธ์** ์ ๋งค์นญ๋ฉ๋๋ค. ๋จ, **๋๋ ํฐ๋ฆฌ ๊ตฌ๋ถ์(`/`)๋ฅผ ๋์ด๊ฐ ์ ์์ต๋๋ค.** (ํ์ฌ ๋๋ ํฐ๋ฆฌ/ํ์ผ ์ด๋ฆ ๋ด์์๋ง ์ ํจ)
+
+```java
+// ์์: *.txt
+"file.txt" (O)
+"a.txt" (O)
+".txt" (O) // ์ด๋ฆ ์๋ ๊ฒฝ์ฐ๋ ๋งค์นญ (๊ตฌํ์ฒด์ ๋ฐ๋ผ ๋ค๋ฆ)
+"dir/file.txt" (X) // / ๋ฅผ ๋์ด๊ฐ ์ ์์
+```
+
+### 4.3 `**` (๋๋ธ ์ ์คํฐ๋ฆฌ์คํฌ)
+
+**0๊ฐ ์ด์์ ๋๋ ํฐ๋ฆฌ(ํจ์ค)** ์ ๋งค์นญ๋ฉ๋๋ค.
+
+```java
+// ์์: /project/**/test
+"/project/test" (O) // ์ค๊ฐ ๋๋ ํฐ๋ฆฌ 0๊ฐ
+"/project/a/test" (O) // ์ค๊ฐ ๋๋ ํฐ๋ฆฌ 1๊ฐ
+"/project/a/b/c/d/test" (O) // ์ค๊ฐ ๋๋ ํฐ๋ฆฌ ๋ค์
+
+// ์์: /static/**
+"/static/css/style.css" (O)
+"/static/images/logo.png" (O)
+```
+
+-----
+
+## 5. ์ค์ ๋น๊ต: Ant Pattern vs Regex
+
+Spring Framework์ `AntPathMatcher`๊ฐ ๋ด๋ถ์ ์ผ๋ก ์ด๋ป๊ฒ ๋์ํ๋์ง ์ดํดํ๊ธฐ ์ํด ๊ฐ์ ์๋๋ฅผ ๊ฐ์ง ํจํด์ ๋น๊ตํด ๋ด
๋๋ค.
+
+### ์๋๋ฆฌ์ค 1: ๋ชจ๋ .jsp ํ์ผ ์ฐพ๊ธฐ
+
+ - **Ant Pattern**: `/**/*.jsp`
+ - **Regex**: `^/.*.jsp$` (๋๋ ๊ฒฝ๋ก ๊ตฌ๋ถ์๋ฅผ ๋ช
ํํ ํ๋ ค๋ฉด `^/.*[^/].jsp$`)
+ - *ํด์*: Ant ํจํด์ด ํจ์ฌ ์ง๊ด์ ์
๋๋ค.
+
+### ์๋๋ฆฌ์ค 2: /admin ํ์์ ๋ชจ๋ URL (๊น์ด ๋ฌด๊ด)
+
+ - **Ant Pattern**: `/admin/**`
+ - **Regex**: `^/admin(/.*)?$`
+ - *ํด์*: Regex๋ ํ์ ๊ฒฝ๋ก๊ฐ ์์ ์๋ ๊ฒฝ์ฐ์ ์๋ ๊ฒฝ์ฐ๋ฅผ ๋ชจ๋ ๊ณ ๋ คํ๋ ๊ทธ๋ฃนํ๊ฐ ํ์ํฉ๋๋ค.
+
+-----
+
+## 6. Java(Spring)์์์ ํ์ฉ ์์
+
+Java Spring ์ํ๊ณ์์๋ `AntPathMatcher` ์ ํธ๋ฆฌํฐ ํด๋์ค๋ฅผ ํตํด ์ด ํจํด์ ์ง์ํฉ๋๋ค.
+
+```java
+import org.springframework.util.AntPathMatcher;
+
+public class AntPatternExample {
+ public static void main(String[] args) {
+ AntPathMatcher matcher = new AntPathMatcher();
+
+ // 1. ? ๋งค์นญ
+ System.out.println(matcher.match("t?st.jsp", "test.jsp")); // true
+
+ // 2. * ๋งค์นญ (๊ฐ์ ๋ ๋ฒจ)
+ System.out.println(matcher.match("*.jsp", "hello.jsp")); // true
+ System.out.println(matcher.match("*.jsp", "a/hello.jsp")); // false (*์ /๋ฅผ ๋ชป ๋์)
+
+ // 3. ** ๋งค์นญ (ํ์ ๊ฒฝ๋ก ํฌํจ)
+ System.out.println(matcher.match("/**/api", "/v1/api")); // true
+ System.out.println(matcher.match("/app/**/*.html", "/app/views/home.html")); // true
+ }
+}
+```
+
+### ์ฃผ์ ํ์ฉ์ฒ
+
+1. **URL ๋งคํ**: `@RequestMapping("/api/**")`, ``
+2. **Spring Security**: `antMatchers("/admin/**").hasRole("ADMIN")`
+3. **ํ์ผ ๊ฒ์**: `PathMatchingResourcePatternResolver`๋ฅผ ํตํ ํด๋์คํจ์ค ๋ด ํ์ผ ๋ก๋
diff --git "a/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/README.md" "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/README.md"
new file mode 100644
index 0000000..d455fdb
--- /dev/null
+++ "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/README.md"
@@ -0,0 +1,4 @@
+# ์ ๊ท ํํ์๊ณผ ANT ํํ์
+1.1 (์ ๊ท ํํ์(Regular Expression))[./Regular_Expression.md]
+1.1.1 (์ ๊ท ํํ์ ์์ )[./Regular_Expression_Example.md]
+1.2 (ANT ํํ์(ANT Expression))[./ANT_Style_Pattern.md]
diff --git "a/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/Regular_Expression.md" "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/Regular_Expression.md"
new file mode 100644
index 0000000..4216d64
--- /dev/null
+++ "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/Regular_Expression.md"
@@ -0,0 +1,512 @@
+
+# ์ ๊ทํํ์(Regular Expression : Regex, Regexp)
+
+## ๐ก ํต์ฌ ์์ฝ๐ก
+> - **ํ ์ค ์ ์** : ๋ฌธ์์ด์์ ํน์ ํจํด์ ์ฐพ๊ฑฐ๋ ์กฐ์ํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ์ผ์ข
์ ํ์ํ๋ ๋ฌธ์์ด์
๋๋ค.
+>
+> - **ํต์ฌ ํค์๋** : `ํจํด ๋งค์นญ`, `๋ฉํ๋ฌธ์`, `๋ฌธ์ ํด๋์ค`, `์๋์`, `์ต์ปค`, `๊ทธ๋ฃนํ`
+>
+> - **์ ์ค์ํ๊ฐ?** : ํ
์คํธ ์ฒ๋ฆฌ์ ๋ฐ์ดํฐ ๊ฒ์ฆ์์ ๊ฐ๋ ฅํ ๋๊ตฌ๋ก ํ์ฉ๋๋ฉฐ, ๋ณต์กํ ๋ฌธ์์ด ๊ฒ์, ์นํ, ์ถ์ถ ์์
์ ํจ์จ์ ์ผ๋ก ์ํํ ์ ์์ด ๊ฐ๋ฐ์์ ๋ฐ์ดํฐ ๊ณผํ์์๊ฒ ํ์์ ์ธ ๊ธฐ์ ์
๋๋ค.
+
+## 1. ๊ฐ๋
+
+ํน์ ํจํด์ ๊ฐ์ง ๋ฌธ์์ด์ ์ฐพ๊ฑฐ๋ ์กฐ์ํ๊ธฐ ์ํ ํ์ ์ธ์ด(Formal Language)์
๋๋ค.
+
+์ ๊ทํํ์์ ๋ค์ํ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ๋๊ตฌ์์ ์ง์๋๋ฉฐ, ๋ณต์กํ ๋ฌธ์์ด ๊ฒ์, ์นํ, ์ถ์ถ ์์
์ ๊ฐ๋จํ๊ฒ ์ํํ ์ ์์ต๋๋ค.
+
+ํ์ง๋ง ์์ฝ๊ฒ๋ **ํ๋์ ํ์ค์ด ์กด์ฌํ์ง ์์ ์ธ์ด๋ ๊ตฌํ์ฒด๋ง๋ค ๋ฌธ๋ฒ๊ณผ ๊ธฐ๋ฅ์ด ๋ค๋ฆ
๋๋ค.**
+
+๋ง์ฝ ์๋ฐ์ ์๋ฐ์คํฌ๋ฆฝํธ๋ก ๊ฐ๋ฐ์ ํ ๋, ๊ฐ์ ๋ฌธ๋ฒ์ผ๋ก ์ ๊ท ํํ์์ ์์ฑํ๋ฉด ํธํ์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
+
+---
+
+## 2. ์ ํ์ํ๊ฐ?
+
+### ํ
์คํธ ์ฒ๋ฆฌ์ ๋ณต์ก์ฑ
+- ๋๋์ ํ
์คํธ ๋ฐ์ดํฐ์์ ํน์ ํจํด์ ์ฐพ๊ฑฐ๋ ๋ณํํ๋ ์์
์ ๋งค์ฐ ๋ณต์กํ ์ ์์ต๋๋ค.
+- ์๋ฅผ ๋ค์ด, ์ด๋ฉ์ผ ์ฃผ์, ์ ํ๋ฒํธ, ์ฐํธ๋ฒํธ ๋ฑ ํน์ ํ์์ ๊ฐ์ง ๋ฐ์ดํฐ๋ฅผ ์ถ์ถํ๊ฑฐ๋ ๊ฒ์ฆํ๋ ์์
์ด ํ์ํ ๋ ์ ๊ทํํ์์ด ์ ์ฉํฉ๋๋ค.
+
+### ํจ์จ์ ์ธ ๋ฌธ์์ด ์กฐ์
+- ์ ๊ทํํ์์ ์ฌ์ฉํ๋ฉด ๋ณต์กํ ๋ฌธ์์ด ์กฐ์ ์์
์ ๊ฐ๊ฒฐํ ์ฝ๋๋ก ํํํ ์ ์์ต๋๋ค.
+- ๋ฐ๋ณต๋ฌธ๊ณผ ์กฐ๊ฑด๋ฌธ์ ์ฌ์ฉํ์ง ์๊ณ ๋ ํจํด ๋งค์นญ๊ณผ ์นํ์ ์ํํ ์ ์์ด ์ฝ๋์ ๊ฐ๋
์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ด ํฅ์๋ฉ๋๋ค.
+
+### ๋ค์ํ ์์ฉ ๋ถ์ผ
+- ๋ฐ์ดํฐ ๊ฒ์ฆ: ์ฌ์ฉ์ ์
๋ ฅ์ด ํน์ ํ์์ ๋ฐ๋ฅด๋์ง ํ์ธ
+- ๋ก๊ทธ ๋ถ์: ๋ก๊ทธ ํ์ผ์์ ํน์ ์ด๋ฒคํธ๋ ์ค๋ฅ ๋ฉ์์ง ์ถ์ถ
+- ์น ์คํฌ๋ํ: HTML ๋ฌธ์์์ ์ํ๋ ๋ฐ์ดํฐ ์ถ์ถ
+
+---
+
+## 3. ์ปดํจํฐ ๊ณผํ ๋ด์์ ์ ๊ทํํ์์ ์์น
+### ์ด์คํค ์๊ณ(Chomsky Hierarchy)
+
+๋
ธ์ ์ด์คํค(Avram Noam Chomsky)๊ฐ 1956๋
์ ์ ์ํ ํ์ ์ธ์ด ๋ถ๋ฅ ์ฒด๊ณ๋ก, ์ธ์ด์ ์์ฑ ๊ท์น์ ๋ฐ๋ผ 4๊ฐ์ง ์ ํ์ผ๋ก ๋๋๋๋ค.
+
+
+- Type 0 : **๋ฌด์ ํ ๋ฌธ๋ฒ(Unrestricted Grammar : UG)**
+
+ Recognized By Turing Machine
+
+ ์์ฑ ๊ท์น : ฮฑAฮฒ โ ฮฒ (ฮฑ, ฮฒ๋ ์์์ ๋ฌธ์์ด)
+
+- Type 1 : **๋ฌธ๋งฅ ๋ฏผ๊ฐ ๋ฌธ๋ฒ(Context-Sensitive Grammar : CSG)**
+
+ Accepted By Linear Bounded Automaton
+
+ ์์ฑ ๊ท์น : ฮฑAฮฒ โ ฮฑฮณฮฒ (ฮณ๋ ๊ณต๋ฐฑ์ด ์๋ ๋ฌธ์์ด)
+
+- Type 2 : **๋ฌธ๋งฅ ์์ ๋ฌธ๋ฒ (Context-Free Grammar : CFG)**
+
+ Accepted By Pushdown Automaton
+
+ ์์ฑ ๊ท์น : A โ ฮฑ (A๋ ๋จ์ผ ๋น๋จ๋ง ๊ธฐํธ, ฮฑ๋ ์์์ ๋ฌธ์์ด)
+
+- Type 3 : **์ ๊ท ๋ฌธ๋ฒ (Regular Grammar : RG)** <- ์ ๊ทํํ์์ ์์น
+
+ Accepted By Finite Automaton
+
+ ์์ฑ ๊ท์น : A โ aB ๋๋ A โ a (A, B๋ ๋จ์ผ ๋น๋จ๋ง ๊ธฐํธ, a๋ ๋จ์ผ ๋จ๋ง ๊ธฐํธ)
+
+### ์ ๊ท ํํ์๊ณผ ์ ํ ์คํ ๋งํ, ์ ๊ท ์ธ์ด
+- ์ ๊ท ์ธ์ด(Regular Language)๋ ์ ๊ท ๋ฌธ๋ฒ์ผ๋ก ์์ฑ๋๋ ์ธ์ด์
๋๋ค. ๋ค๋ฅธ๋ง๋ก ํ๋ฉด ์ ๊ท ํํ์์ผ๋ก ํํ ๊ฐ๋ฅํ ์ธ์ด๋ผ๊ณ ๋ ํ ์ ์์ต๋๋ค.
+- ์ ํ ์คํ ๋งํ(Finite Automaton)๋ ์ ๊ท ์ธ์ด๋ฅผ ์ธ์ํ๋ ์ถ์ ๊ธฐ๊ณ์
๋๋ค. ์ ๊ท ํํ์๊ณผ ์ ํ ์คํ ๋งํ๋ ์๋ก ๋ณํ์ด ๊ฐ๋ฅํ๋ฉฐ, ๋์ผํ ์ธ์ด๋ฅผ ํํํ ์ ์์ต๋๋ค.
+```
+์ ๊ท ํํ์ <-> ์ ํ ์คํ ๋งํ <-> ์ ๊ท ๋ฌธ๋ฒ
+```
+
+### ์ ๊ท ํํ์ ์์ง์ ๊ธฐ๋ณธ ๊ฐ๋
+- **NFA (Nondeterministic Finite Automaton)** : ๋น๊ฒฐ์ ์ ์ ํ ์คํ ๋งํ
+ - ์ฌ๋ฌ ๊ฒฝ๋ก๋ฅผ ๋์์ ํ์ (๋ฐฑํธ๋ํน ์ฌ์ฉ)
+ - ๋๋ฆฌ์ง๋ง ๋ณต์กํ ํจํด ์ฒ๋ฆฌ์ ์ ๋ฆฌ
+ - ๋ฐฑํธ๋ํน์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ต์
์ ๊ฒฝ์ฐ O(2^n) ์๊ฐ ๋ณต์ก๋๋ฅผ ๊ฐ์ง ์ ์์
+ - ์ด๋ฐ ๋ถ๋ถ์ ๊ณต๊ฒฉํ๋ฉด ReDoS (Regular Expression Denial of Service) ๊ณต๊ฒฉ์ด ๋ฐ์ํ ์ ์์
+ - ์บก์ฒ ๊ทธ๋ฃน, ์ญ์ฐธ์กฐ ์ง์
+ - Java, JavaScript, Python ๋ฑ์์ ์ฌ์ฉ
+
+- **DFA (Deterministic Finite Automaton)** : ๊ฒฐ์ ์ ์ ํ ์คํ ๋งํ
+ - ๋จ์ผ ๊ฒฝ๋ก๋ง ํ์
+ - ๋น ๋ฅด์ง๋ง ๋ณต์กํ ํจํด ์ฒ๋ฆฌ์ ๋ถ๋ฆฌ
+ - ์บก์ฒ ๊ทธ๋ฃน, ์ญ์ฐธ์กฐ ๋ฏธ์ง์
+ - grep, awk, sed ๋ฑ์์ ์ฌ์ฉ
+
+---
+
+## 4. ์ ๊ท ํํ์์ ์ฃผ์ ๊ตฌ์ฑ ์์
+
+์ ๊ท ํํ์์ ํฌ๊ฒ ๋ฆฌํฐ๋ด, ๋ฉํ ๋ฌธ์, ๋ฌธ์ ํด๋์ค, ์๋์, ์ต์ปค, ๊ทธ๋ฃนํ ๋ฑ์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. ๊ฐ ๊ตฌ์ฑ ์์๋ ํจํด์ ์ ์ํ๋ ๋ฐ ๊ณ ์ ํ ์ญํ ์ ํฉ๋๋ค.
+์์์ ์ด์ผ๊ธฐ ํ๋๋๋ก ํ๋์ ํ์ค์ด ์กด์ฌํ์ง๋ ์์ง๋ง ์์๋๋ฉด ์ข์ ๊ฒ ๊ฐ์์ ์ฃผ์ ๊ตฌ์ฑ ์์๋ฅผ ์ ๋ฆฌํด๋ดค์ต๋๋ค.
+
+### 4.1 ๋ฆฌํฐ๋ด (Literals)
+๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ํํ์ ์ ๊ท ํํ์์ผ๋ก, ์๋ ๊ทธ๋๋ก์ ๋ฌธ์๋ฅผ ๋งค์นญํฉ๋๋ค.
+
+```java
+// ์ผ๋ฐ ๋ฌธ์์ด ๋งค์นญ
+String pattern = "hello";
+"hello world".matches(".*hello.*"); // true
+"Hello world".matches(".*hello.*"); // false (๋์๋ฌธ์ ๊ตฌ๋ถ)
+
+// ๋์๋ฌธ์ ๋ฌด์ (Pattern.CASE_INSENSITIVE ํ๋๊ทธ ์ฌ์ฉ)
+Pattern p = Pattern.compile("hello", Pattern.CASE_INSENSITIVE);
+p.matcher("Hello world").find(); // true
+```
+
+### 4.2 ๋ฉํ ๋ฌธ์ (Meta-characters)
+์ ๊ท ํํ์์์ ํน๋ณํ ์๋ฏธ๋ฅผ ๊ฐ์ง๋ ๋ฌธ์๋ค์
๋๋ค. ์ด ๋ฌธ์๋ค์ ๋ฆฌํฐ๋ด๋ก ์ฌ์ฉํ๋ ค๋ฉด ๋ฐฑ์ฌ๋์(`\`)๋ก ์ด์ค์ผ์ดํํด์ผ ํฉ๋๋ค.
+์ฃผ์ ๋ฉํ ๋ฌธ์: `. ^ $ * + ? { } [ ] \ | ( )`
+
+### 4.2.1 `.` (Any character)
+๊ฐํ ๋ฌธ์(`\n`)๋ฅผ ์ ์ธํ ๋ชจ๋ ๋จ์ผ ๋ฌธ์์ ๋งค์นญ๋ฉ๋๋ค.
+
+```java
+// . ์ ๊ฐํ ๋ฌธ์๋ฅผ ์ ์ธํ ๋ชจ๋ ๋ฌธ์ ํ๋์ ๋งค์นญ
+"cat".matches("c.t"); // true
+"cut".matches("c.t"); // true
+"ct".matches("c.t"); // false (๋ฌธ์ ํ๋ ํ์)
+"c\\nt".matches("c.t"); // false (๊ฐํ ์ ์ธ)
+
+// DOTALL ๋ชจ๋๋ก ๊ฐํ๋ ํฌํจ
+Pattern.compile("c.t", Pattern.DOTALL).matcher("c\\nt").matches(); // true
+```
+
+### 4.2.2 `|` (Alternation / OR)
+๋ ์ค ํ๋๋ฅผ ์ ํํ๋ OR ์ฐ์ฐ์์
๋๋ค.
+
+```java
+// OR ์ฐ์ฐ
+"cat".matches("cat|dog"); // true
+"dog".matches("cat|dog"); // true
+"bird".matches("cat|dog"); // false
+
+// ๊ทธ๋ฃน๊ณผ ํจ๊ป ์ฌ์ฉ
+"gmail.com".matches("(gmail|naver|daum)\\\\.com"); // true
+```
+
+### 4.2.3 `\` (Escape Character)
+๋ฉํ ๋ฌธ์๋ฅผ ์ผ๋ฐ ๋ฌธ์๋ก ์ทจ๊ธํ๊ฒ ํ๊ฑฐ๋, ํน์ํ ๋ฌธ์ ํด๋์ค๋ฅผ ์ ์ํ ๋ ์ฌ์ฉํฉ๋๋ค.
+Java ๋ฌธ์์ด์์๋ ๋ฐฑ์ฌ๋์ ์์ฒด๋ฅผ ์ด์ค์ผ์ดํํด์ผ ํ๋ฏ๋ก `\\`๋ก ์์ฑํด์ผ ํฉ๋๋ค.
+
+```java
+// ๋ฉํ ๋ฌธ์ . ์ ์ผ๋ฐ ๋ฌธ์๋ก ๋งค์นญ
+"abc.def".matches("abc\\\\.def"); // true
+"abc.def".matches("abc.def"); // true (.์ด ๋ชจ๋ ๋ฌธ์์ ๋งค์นญ๋๋ฏ๋ก)
+"abcXdef".matches("abc\\\\.def"); // false
+```
+
+### 4.3 ๋ฌธ์ ํด๋์ค (Character Classes)
+๋๊ดํธ `[]` ์์ ํฌํจ๋ ๋ฌธ์ ์ค ํ๋์ ๋งค์นญ๋ฉ๋๋ค.
+
+```java
+// [abc] : a, b, c ์ค ํ๋
+"a".matches("[abc]"); // true
+"d".matches("[abc]"); // false
+
+// [^abc] : a, b, c๋ฅผ ์ ์ธํ ๋ชจ๋ ๋ฌธ์ (๋ถ์ )
+"d".matches("[^abc]"); // true
+"a".matches("[^abc]"); // false
+
+// [a-z] : ๋ฒ์ ์ง์ (Range)
+"m".matches("[a-z]"); // true (์๋ฌธ์)
+"M".matches("[A-Z]"); // true (๋๋ฌธ์)
+"5".matches("[0-9]"); // true (์ซ์)
+
+// [a-zA-Z] : ์กฐํฉ (Union)
+"a".matches("[a-zA-Z]"); // true
+
+// [a-z&&[def]] : ๊ต์งํฉ (Intersection) - Java ์ ๊ท์ ํน์ง
+"d".matches("[a-z&&[def]]"); // true (d, e, f ์ค ํ๋์ด๋ฉด์ ์๋ฌธ์)
+"a".matches("[a-z&&[def]]"); // false
+```
+
+### 4.4 ์ฌ์ ์ ์๋ ๋ฌธ์ ํด๋์ค (Predefined Character Classes / Shorthands)
+์์ฃผ ์ฌ์ฉ๋๋ ๋ฌธ์ ํด๋์ค๋ฅผ ์งง๊ฒ ํํํ ๊ฒ์
๋๋ค.
+
+```java
+// \\d - Digit (์ซ์) = [0-9]
+"5".matches("\\\\d"); // true
+"a".matches("\\\\d"); // false
+
+// \\D - Non-Digit (์ซ์ ์๋) = [^0-9]
+"a".matches("\\\\D"); // true
+
+// \\w - Word character (๋จ์ด ๋ฌธ์) = [a-zA-Z0-9_]
+"a".matches("\\\\w"); // true
+"_".matches("\\\\w"); // true
+"@".matches("\\\\w"); // false (ํน์๋ฌธ์ ์ ์ธ)
+
+// \\W - Non-Word character = [^a-zA-Z0-9_]
+"@".matches("\\\\W"); // true
+
+// \\s - Whitespace (๊ณต๋ฐฑ) = [ \\t\\n\\r\\f]
+" ".matches("\\\\s"); // true
+"\\t".matches("\\\\s"); // true
+
+// \\S - Non-Whitespace = [^\\s]
+"a".matches("\\\\S"); // true
+```
+
+### 4.5 ์๋์ (Quantifiers)
+์์ ์์๊ฐ ์ผ๋ง๋ ๋ฐ๋ณต๋๋์ง๋ฅผ ์ง์ ํฉ๋๋ค.
+
+#### 4.5.1 ๊ธฐ๋ณธ ์๋์
+```java
+// * : 0ํ ์ด์ ({0,})
+"".matches("a*"); // true
+"aaa".matches("a*"); // true
+
+// + : 1ํ ์ด์ ({1,})
+"".matches("a+"); // false
+"a".matches("a+"); // true
+
+// ? : 0ํ ๋๋ 1ํ ({0,1})
+"a".matches("a?"); // true
+"aa".matches("a?"); // false
+
+// {n} : ์ ํํ nํ
+"aa".matches("a{2}"); // true
+
+// {n,m} : nํ ์ด์ mํ ์ดํ
+"aaa".matches("a{2,4}"); // true
+
+// {n,} : nํ ์ด์
+"aaa".matches("a{2,}"); // true
+```
+
+#### 4.5.2 ์๋์์ ์ข
๋ฅ (Greedy, Lazy, Possessive)
+| ์ข
๋ฅ | ๋ฌธ๋ฒ | ์ค๋ช
| ์์ (`aab` ์์ `a+` ๋งค์นญ) |
+|---|---|---|---|
+| **Greedy** (ํ์์ ) | `*`, `+`, `?`, `{n,m}` | ๊ฐ๋ฅํ ํ **๊ฐ์ฅ ๋ง์ด** ๋งค์นญํ๋ ค๊ณ ์๋ํฉ๋๋ค. (๊ธฐ๋ณธ๊ฐ) | `aa` (์ ์ฒด ๋งค์นญ) |
+| **Reluctant** (Lazy, ๊ฒ์ผ๋ฅธ) | `*?`, `+?`, `??`, `{n,m}?` | ๊ฐ๋ฅํ ํ **๊ฐ์ฅ ์ ๊ฒ** ๋งค์นญํ๋ ค๊ณ ์๋ํฉ๋๋ค. | `a` (์ฒซ ๋ฒ์งธ a๋ง ๋งค์นญ) |
+| **Possessive** (์์ ์ ) | `*+`, `++`, `?+`, `{n,m}+` | Greedy์ฒ๋ผ ๋ง์ด ๋งค์นญํ์ง๋ง, **๋ฐฑํธ๋ํน์ ํ์ฉํ์ง ์์ต๋๋ค.** ์ฑ๋ฅ ์ต์ ํ์ ์ฌ์ฉ๋ฉ๋๋ค. | `aa` (๋งค์นญ ํ ๋ค๋ก ๋์๊ฐ์ง ์์) |
+
+```java
+// Greedy vs Lazy
+String text = "
Hello
";
+// Greedy: Hello
์ ์ฒด ๋งค์นญ
+text.matches(".*
");
+// Lazy: Hello
์์ ,
๊ฐ๊ฐ ๋งค์นญ ์๋ ๊ฐ๋ฅ (find() ์ฌ์ฉ ์)
+```
+
+### 4.6 ๊ฒฝ๊ณ (Anchors)
+๋ฌธ์์ด์ ํน์ ์์น๋ฅผ ์ง์ ํฉ๋๋ค. ๋ฌธ์๋ฅผ ์๋นํ์ง ์๊ณ ์์น๋ง ํ์ธํฉ๋๋ค (Zero-width assertion).
+
+```java
+// ^ : ๋ฌธ์์ด(๋๋ ๋ผ์ธ)์ ์์
+Pattern.compile("^hello").matcher("hello world").find(); // true
+
+// $ : ๋ฌธ์์ด(๋๋ ๋ผ์ธ)์ ๋
+Pattern.compile("world$").matcher("hello world").find(); // true
+
+// \\b : ๋จ์ด ๊ฒฝ๊ณ (Word Boundary) - ๋ฌธ์์ ๊ณต๋ฐฑ/ํน์๋ฌธ์ ์ฌ์ด
+"cat".matches("\\\\bcat\\\\b"); // true
+"cathedral".matches("\\\\bcat\\\\b"); // false (cat์ด ๋จ์ด์ ์ผ๋ถ์)
+
+// \\B : ๋จ์ด ๊ฒฝ๊ณ๊ฐ ์๋
+"cathedral".matches(".*\\\\Bcat\\\\B.*"); // true (์ค๊ฐ์ ํฌํจ๋ cat)
+
+// \\A : ์
๋ ฅ์ ์์ (๋ฌด์กฐ๊ฑด ๋ฌธ์์ด ์ ์ฒด์ ์์)
+// \\z : ์
๋ ฅ์ ๋ (๋ฌด์กฐ๊ฑด ๋ฌธ์์ด ์ ์ฒด์ ๋)
+// \\Z : ์
๋ ฅ์ ๋ (๋ง์ง๋ง ์ข
๊ฒฐ์(\\n)๊ฐ ์์ผ๋ฉด ๊ทธ ์)
+```
+### 4.7 ๊ทธ๋ฃนํ (Grouping)
+์ฌ๋ฌ ๋ฌธ์๋ฅผ ํ๋์ ๋จ์๋ก ๋ฌถ๊ฑฐ๋, ๋งค์นญ๋ ๋ถ๋ถ์ ์ถ์ถํ ๋ ์ฌ์ฉํฉ๋๋ค.
+
+#### 4.7.1 ์บก์ฒ ๊ทธ๋ฃน (Capturing Groups) - `(...)`
+๋งค์นญ๋ ๋ถ๋ถ์ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅํ์ฌ ๋์ค์ ์ฐธ์กฐํ ์ ์์ต๋๋ค.
+
+```java
+String log = "2025-11-18 23:43:21 ERROR User login failed";
+
+Pattern p = Pattern.compile("(\\\\d{4}-\\\\d{2}-\\\\d{2}) (\\\\d{2}:\\\\d{2}:\\\\d{2}) (\\\\w+) (.+)");
+Matcher m = p.matcher(log);
+
+if (m.find()) {
+ System.out.println("๋ ์ง: " + m.group(1)); // 2025-11-18
+ System.out.println("์๊ฐ: " + m.group(2)); // 23:43:21
+ System.out.println("๋ ๋ฒจ: " + m.group(3)); // ERROR
+ System.out.println("๋ฉ์์ง: " + m.group(4)); // User login failed
+}
+```
+
+#### 4.7.2 ๋น์บก์ฒ ๊ทธ๋ฃน (Non-Capturing Groups) - `(?:...)`
+๊ทธ๋ฃนํ๋ ํ์ํ์ง๋ง, ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅํ ํ์๊ฐ ์์ ๋ ์ฌ์ฉํฉ๋๋ค. ์ฑ๋ฅ์ ์ด์ ์ด ์์ต๋๋ค.
+
+```java
+// ๊ทธ๋ฃนํ๋ ํ์ํ์ง๋ง ์บก์ฒ๋ ๋ถํ์ํ ๊ฒฝ์ฐ
+String url = "";
+
+// ๋น์บก์ฒ ๊ทธ๋ฃน ์ฌ์ฉ (์ฑ๋ฅ ํฅ์)
+Pattern p2 = Pattern.compile("(?:https|http)://(.+)");
+Matcher m2 = p2.matcher(url);
+if (m2.find()) {
+ // m2.group(1)์ ์ด์ www.example.com (ํ๋กํ ์ฝ์ ์บก์ฒ ์ ๋จ)
+ System.out.println(m2.group(1)); // www.example.com
+}
+```
+
+#### 4.7.3 ๋ช
๋ช
๋ ๊ทธ๋ฃน (Named Groups) - `(?...)`
+์ธ๋ฑ์ค ๋์ ์ด๋ฆ์ผ๋ก ๊ทธ๋ฃน์ ์ฐธ์กฐํ ์ ์์ด ๊ฐ๋
์ฑ์ด ์ข์์ง๋๋ค.
+
+```java
+String date = "2025-11-18";
+Pattern p = Pattern.compile("(?\\\\d{4})-(?\\\\d{2})-(?\\\\d{2})");
+Matcher m = p.matcher(date);
+
+if (m.find()) {
+ System.out.println("๋
๋: " + m.group("year")); // 2025
+ System.out.println("์: " + m.group("month")); // 11
+ System.out.println("์ผ: " + m.group("day")); // 18
+}
+```
+
+#### 4.7.4 ์ญ์ฐธ์กฐ (Back-reference) - `\\1`, `\\k`
+์์ ๋งค์นญ๋ ๊ทธ๋ฃน์ ๋ค์ ์ฐธ์กฐํฉ๋๋ค.
+
+```java
+// ์ค๋ณต ๋จ์ด ์ฐพ๊ธฐ
+String text = "the the cat sat on the the mat";
+Pattern p = Pattern.compile("\\\\b(\\\\w+)\\\\s+\\\\1\\\\b");
+Matcher m = p.matcher(text);
+
+while (m.find()) {
+ System.out.println("์ค๋ณต ๋จ์ด: " + m.group(1));
+}
+
+// HTML ํ๊ทธ ๋งค์นญ (์ฌ๋ ํ๊ทธ์ ๋ซ๋ ํ๊ทธ๊ฐ ๊ฐ์์ผ ํจ)
+String html = "Title
Content
Subtitle
";
+Pattern tagPattern = Pattern.compile("<(\\\\w+)>.*?\\\\1>");
+Matcher tagMatcher = tagPattern.matcher(html);
+
+while (tagMatcher.find()) {
+ System.out.println(tagMatcher.group());
+}
+```
+
+### 4.8 ํ๋๊ทธ (Flags)
+์ ๊ท ํํ์์ ๋์ ๋ฐฉ์์ ๋ณ๊ฒฝํ๋ ์ต์
์
๋๋ค. `Pattern.compile()`์ ๋ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌํ๊ฑฐ๋, ํจํด ๋ด์ `(?flags)` ํํ๋ก ํฌํจํ ์ ์์ต๋๋ค.
+
+- `Pattern.CASE_INSENSITIVE` (`(?i)`): ๋์๋ฌธ์๋ฅผ ๊ตฌ๋ถํ์ง ์์ต๋๋ค.
+- `Pattern.MULTILINE` (`(?m)`): `^`์ `$`๊ฐ ์ ์ฒด ๋ฌธ์์ด์ด ์๋ ๊ฐ ๋ผ์ธ์ ์์๊ณผ ๋์ ๋งค์นญ๋ฉ๋๋ค.
+- `Pattern.DOTALL` (`(?s)`): `.`์ด ๊ฐํ ๋ฌธ์๋ฅผ ํฌํจํ ๋ชจ๋ ๋ฌธ์์ ๋งค์นญ๋ฉ๋๋ค.
+- `Pattern.COMMENTS` (`(?x)`): ํจํด ๋ด์ ๊ณต๋ฐฑ๊ณผ ์ฃผ์์ ๋ฌด์ํฉ๋๋ค. (๊ฐ๋
์ฑ ํฅ์)
+
+```java
+// MULTILINE ์์
+String text = "First line\\nSecond line";
+Pattern p = Pattern.compile("^Second", Pattern.MULTILINE); // ๊ฐ ์ค์ ์์์์ ๋งค์นญ
+Matcher m = p.matcher(text);
+System.out.println(m.find()); // true
+```
+
+---
+
+## 5. ์ฌํ ๊ฐ๋
(Advanced Concepts)
+
+### 5.1 Greedy vs. Lazy ์์ธ ์์
+
+#### Greedy (ํ์์ ) - ๊ธฐ๋ณธ ๋์
+
+```java
+String html = "Hello
World
";
+
+// Greedy: ์ต๋ํ ๋ง์ด ๋งค์นญ
+Pattern greedy = Pattern.compile(".*
");
+Matcher m = greedy.matcher(html);
+if (m.find()) {
+ System.out.println(m.group());
+ // ์ถ๋ ฅ: Hello
World
+ // (์ ์ฒด๋ฅผ ํ๋๋ก ๋งค์นญ)
+}
+```
+
+#### Lazy (๊ฒ์ผ๋ฅธ) - `?` ์ถ๊ฐ
+
+```java
+String html = "Hello
World
";
+
+// Lazy: ์ต์ํ๋ง ๋งค์นญ
+Pattern lazy = Pattern.compile(".*?
");
+Matcher m = lazy.matcher(html);
+while (m.find()) {
+ System.out.println(m.group());
+}
+// ์ถ๋ ฅ:
+// Hello
+// World
+// (๊ฐ๊ฐ ๊ฐ๋ณ ๋งค์นญ)
+```
+
+**์๋์๋ณ Lazy ๋ฒ์ **:
+
+```java
+// Greedy โ Lazy
+// * โ *?
+// + โ +?
+// ? โ ??
+// {n,m} โ {n,m}?
+// {n,} โ {n,}?
+
+// ์ค๋ฌด ์์ : HTML ํ๊ทธ ์ถ์ถ
+String html = "First
Second
";
+
+// Greedy
+html.replaceAll(".*
", "[CONTENT]");
+// ๊ฒฐ๊ณผ: [CONTENT]
+
+// Lazy
+html.replaceAll(".*?
", "[CONTENT]");
+// ๊ฒฐ๊ณผ: [CONTENT][CONTENT]
+```
+
+## ๐ 6. ์ฑ๋ฅ ์ต์ ํ ํ
+
+### 6.1 Pattern ์ฌ์ฌ์ฉ
+
+```java
+// โ ๋์ ์: ๋งค๋ฒ ์ปดํ์ผ
+public boolean validateEmail(String email) {
+ return email.matches("^[\\\\w.-]+@[\\\\w.-]+\\\\.[a-zA-Z]{2,}$");
+ // matches()๋ ๋ด๋ถ์์ ๋งค๋ฒ Pattern.compile() ํธ์ถ
+}
+
+// โ
์ข์ ์: Pattern ์ฌ์ฌ์ฉ
+public class EmailValidator {
+ private static final Pattern EMAIL_PATTERN =
+ Pattern.compile("^[\\\\w.-]+@[\\\\w.-]+\\\\.[a-zA-Z]{2,}$");
+
+ public boolean validateEmail(String email) {
+ return EMAIL_PATTERN.matcher(email).matches();
+ }
+}
+
+// ์ฑ๋ฅ ์ฐจ์ด: ์ฝ 3-5๋ฐฐ ๋น ๋ฆ
+
+```
+
+### 6.2 ๋น์บก์ฒ ๊ทธ๋ฃน ์ฌ์ฉ
+
+```java
+// โ ๋๋ฆผ: ๋ถํ์ํ ์บก์ฒ
+Pattern slow = Pattern.compile("(https|http)://([\\\\w.]+)");
+
+// โ
๋น ๋ฆ: ๋น์บก์ฒ ๊ทธ๋ฃน
+Pattern fast = Pattern.compile("(?:https|http)://([\\\\w.]+)");
+// ๋๋ฉ์ธ๋ง ํ์ํ ๊ฒฝ์ฐ ํ๋กํ ์ฝ ์บก์ฒ ๋ถํ์
+
+```
+
+### 6.3 Catastrophic Backtracking ๋ฐฉ์ง
+
+
+
+์ด ์ฌ์์ ๋ฐฑํธ๋ํน์ ์ ๊ทํํ์์ ์ด์ฉํ ๊ณต๊ฒฉ์ธ ReDoS(Regular Expression Denial of Service)์ ์์ธ์ด ๋ ์ ์์ต๋๋ค.
+์ค์ ๋ก 2019๋
์๋ ํด๋ผ์ฐ๋ ํ๋ ์ด์์ ReDos ๊ณต๊ฒฉ์ ๋ฐ์์ต๋๋ค.
+
+๊ฐ๋จํ๊ฒ ํด๊ฒฐ ๊ฐ๋ฅํ์ง๋ง ์ฃผ์ํด์ ์ฌ์ฉํ์ง ์์ผ๋ฉด ์ฝ๊ฒ ๊ณต๊ฒฉ์ ๋ฐ์ ์ ์๋ ๋ถ๋ถ์
๋๋ค.
+
+```java
+// โ ์ํ: ์ฌ์์ ๋ฐฑํธ๋ํน ๊ฐ๋ฅ
+Pattern dangerous = Pattern.compile("(a+)+b");
+String input = "aaaaaaaaaaaaaaaaaaaaaaaaaaac"; // 'b'๊ฐ ์์
+// ์ด ๊ฒฝ์ฐ ์ง์ ์๊ฐ ๋ณต์ก๋๋ก ์ธํด ๋งค์ฐ ๋๋ ค์ง (๋๋ ์คํ ์ค๋ฒํ๋ก์ฐ)
+
+// โ
์์ : Possessive ์๋์ ์ฌ์ฉ
+Pattern safe = Pattern.compile("(a++)b");
+// ๋๋ Atomic ๊ทธ๋ฃน ์ฌ์ฉ
+Pattern safe2 = Pattern.compile("(?>a+)b");
+
+// ์ค๋ฌด์์๋ ํ์์์ ์ค์
+public boolean matchesWithTimeout(String pattern, String input, long timeoutMs) {
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ Future future = executor.submit(() -> {
+ return Pattern.compile(pattern).matcher(input).matches();
+ });
+
+ try {
+ return future.get(timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ future.cancel(true);
+ return false;
+ } catch (Exception e) {
+ return false;
+ } finally {
+ executor.shutdownNow();
+ }
+}
+
+```
+
+---
+
+## ๐ 7. ์ฐธ๊ณ ์๋ฃ ๋ฐ ํ์ต ๋ฆฌ์์ค
+
+### ์จ๋ผ์ธ ๋๊ตฌ
+
+- **Regex101** (https://regex101.com/) - ์ค์๊ฐ ํจํด ํ
์คํธ ๋ฐ ์ค๋ช
+- **RegExr** (https://regexr.com/) - ์๊ฐ์ ๋๋ฒ๊น
+- **RegexPlanet** (https://www.regexplanet.com/) - ๋ค์ํ ์ธ์ด๋ณ ํ
์คํธ
+
+### Java ๊ณต์ ๋ฌธ์
+
+- `java.util.regex.Pattern` JavaDoc
+- `java.util.regex.Matcher` JavaDoc
+
diff --git "a/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/img/chomsky-hierarchy.png" "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/img/chomsky-hierarchy.png"
new file mode 100644
index 0000000..cdedf0e
Binary files /dev/null and "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/img/chomsky-hierarchy.png" differ
diff --git "a/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/presentation/Regular_Expression_Example.md" "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/presentation/Regular_Expression_Example.md"
new file mode 100644
index 0000000..0738cc4
--- /dev/null
+++ "b/76-Temporal/01-\354\240\225\352\267\234\355\221\234\355\230\204\354\213\235&ANT\355\221\234\355\230\204\354\213\235/presentation/Regular_Expression_Example.md"
@@ -0,0 +1,591 @@
+## ๐ผ 1. ์ค์ฉ ์์
+
+### 1.1 ์ ํจ์ฑ ๊ฒ์ฆ (Validation)
+
+### ์ด๋ฉ์ผ ๊ฒ์ฆ
+
+```java
+/**
+ * ์ด๋ฉ์ผ ๊ฒ์ฆ ํจํด
+ * - ๋ก์ปฌ ํํธ: ์๋ฌธ, ์ซ์, ํน์๋ฌธ์(.-_+) ํ์ฉ
+ * - @
+ * - ๋๋ฉ์ธ: ์๋ฌธ, ์ซ์, ํ์ดํ ํ์ฉ
+ * - ์ต์์ ๋๋ฉ์ธ: 2์ ์ด์์ ์๋ฌธ
+ */
+public class EmailValidator {
+
+ // ๊ฐ๋จํ ๋ฒ์
+ private static final String EMAIL_SIMPLE =
+ "^[\\\\w.-]+@[\\\\w.-]+\\\\.[a-zA-Z]{2,}$";
+
+ // RFC 5322 ์ค์ (๋ ์๊ฒฉํ) ๋ฒ์
+ private static final String EMAIL_STRICT =
+ "^[a-zA-Z0-9_+&*-]+(?:\\\\.[a-zA-Z0-9_+&*-]+)*@" +
+ "(?:[a-zA-Z0-9-]+\\\\.)+[a-zA-Z]{2,7}$";
+
+ public static boolean isValidEmail(String email) {
+ if (email == null || email.isEmpty()) {
+ return false;
+ }
+ return email.matches(EMAIL_SIMPLE);
+ }
+
+ // ํ
์คํธ
+ public static void main(String[] args) {
+ String[] emails = {
+ "user@example.com", // true
+ "user.name@example.com", // true
+ "user+tag@example.co.kr", // true
+ "user@", // false
+ "@example.com", // false
+ "user@.com", // false
+ "user name@example.com" // false (๊ณต๋ฐฑ)
+ };
+
+ for (String email : emails) {
+ System.out.printf("%s: %b%n", email, isValidEmail(email));
+ }
+ }
+}
+
+```
+
+### ๋น๋ฐ๋ฒํธ ๊ฒ์ฆ
+
+```java
+/**
+ * ๋น๋ฐ๋ฒํธ ๊ฐ๋ ๊ฒ์ฆ
+ * ์๊ตฌ์ฌํญ:
+ * - 8-20์
+ * - ์ต์ 1๊ฐ์ ๋๋ฌธ์
+ * - ์ต์ 1๊ฐ์ ์๋ฌธ์
+ * - ์ต์ 1๊ฐ์ ์ซ์
+ * - ์ต์ 1๊ฐ์ ํน์๋ฌธ์
+ */
+public class PasswordValidator {
+
+ // Lookahead๋ฅผ ์ฌ์ฉํ ๋ณต์กํ ๊ฒ์ฆ
+ private static final String PASSWORD_PATTERN =
+ "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\\\d)(?=.*[@$!%*?&])[A-Za-z\\\\d@$!%*?&]{8,20}$";
+
+ public static ValidationResult validatePassword(String password) {
+ if (password == null || password.isEmpty()) {
+ return new ValidationResult(false, "๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.");
+ }
+
+ // ๊ธธ์ด ์ฒดํฌ
+ if (password.length() < 8 || password.length() > 20) {
+ return new ValidationResult(false, "๋น๋ฐ๋ฒํธ๋ 8-20์์ฌ์ผ ํฉ๋๋ค.");
+ }
+
+ // ์๋ฌธ์ ํฌํจ ์ฒดํฌ
+ if (!password.matches(".*[a-z].*")) {
+ return new ValidationResult(false, "์๋ฌธ์๋ฅผ ์ต์ 1๊ฐ ํฌํจํด์ผ ํฉ๋๋ค.");
+ }
+
+ // ๋๋ฌธ์ ํฌํจ ์ฒดํฌ
+ if (!password.matches(".*[A-Z].*")) {
+ return new ValidationResult(false, "๋๋ฌธ์๋ฅผ ์ต์ 1๊ฐ ํฌํจํด์ผ ํฉ๋๋ค.");
+ }
+
+ // ์ซ์ ํฌํจ ์ฒดํฌ
+ if (!password.matches(".*\\\\d.*")) {
+ return new ValidationResult(false, "์ซ์๋ฅผ ์ต์ 1๊ฐ ํฌํจํด์ผ ํฉ๋๋ค.");
+ }
+
+ // ํน์๋ฌธ์ ํฌํจ ์ฒดํฌ
+ if (!password.matches(".*[@$!%*?&].*")) {
+ return new ValidationResult(false, "ํน์๋ฌธ์(@$!%*?&)๋ฅผ ์ต์ 1๊ฐ ํฌํจํด์ผ ํฉ๋๋ค.");
+ }
+
+ return new ValidationResult(true, "์์ ํ ๋น๋ฐ๋ฒํธ์
๋๋ค.");
+ }
+
+ // ํตํฉ ํจํด ์ฌ์ฉ (ํ ๋ฒ์ ์ฒดํฌ)
+ public static boolean isValidPasswordQuick(String password) {
+ return password != null && password.matches(PASSWORD_PATTERN);
+ }
+
+ static class ValidationResult {
+ boolean valid;
+ String message;
+
+ ValidationResult(boolean valid, String message) {
+ this.valid = valid;
+ this.message = message;
+ }
+ }
+
+ // ํ
์คํธ
+ public static void main(String[] args) {
+ String[] passwords = {
+ "Abc123!@", // true
+ "Password123!", // true
+ "weak", // false (๋๋ฌด ์งง์)
+ "alllowercase123!", // false (๋๋ฌธ์ ์์)
+ "ALLUPPERCASE123!", // false (์๋ฌธ์ ์์)
+ "NoSpecialChar123", // false (ํน์๋ฌธ์ ์์)
+ "NoNumber!@#", // false (์ซ์ ์์)
+ };
+
+ for (String pw : passwords) {
+ ValidationResult result = validatePassword(pw);
+ System.out.printf("%s: %b - %s%n", pw, result.valid, result.message);
+ }
+ }
+}
+
+```
+
+### ํ๊ตญ ์ ํ๋ฒํธ ๊ฒ์ฆ
+
+```java
+public class PhoneValidator {
+
+ // ํด๋ํฐ: 010-XXXX-XXXX
+ private static final String MOBILE_PATTERN = "^010-\\\\d{4}-\\\\d{4}$";
+
+ // ์ผ๋ฐ ์ ํ: 0XX-XXX(X)-XXXX
+ private static final String PHONE_PATTERN = "^0\\\\d{1,2}-\\\\d{3,4}-\\\\d{4}$";
+
+ // ํตํฉ ํจํด
+ private static final String ALL_PHONE_PATTERN =
+ "^(010-\\\\d{4}-\\\\d{4}|0\\\\d{1,2}-\\\\d{3,4}-\\\\d{4})$";
+
+ public static boolean isValidMobile(String phone) {
+ return phone != null && phone.matches(MOBILE_PATTERN);
+ }
+
+ public static boolean isValidPhone(String phone) {
+ return phone != null && phone.matches(ALL_PHONE_PATTERN);
+ }
+
+ // ํ์ดํ ์๋ ์ถ๊ฐ
+ public static String formatPhone(String phone) {
+ if (phone == null) return null;
+
+ // ์ซ์๋ง ์ถ์ถ
+ String numbers = phone.replaceAll("[^0-9]", "");
+
+ // ํด๋ํฐ (11์๋ฆฌ)
+ if (numbers.matches("^010\\\\d{8}$")) {
+ return numbers.replaceAll("(\\\\d{3})(\\\\d{4})(\\\\d{4})", "$1-$2-$3");
+ }
+
+ // ์์ธ (10์๋ฆฌ: 02-XXXX-XXXX)
+ if (numbers.matches("^02\\\\d{8}$")) {
+ return numbers.replaceAll("(\\\\d{2})(\\\\d{4})(\\\\d{4})", "$1-$2-$3");
+ }
+
+ // ๊ธฐํ ์ง์ญ (10์๋ฆฌ: 0XX-XXX-XXXX)
+ if (numbers.matches("^0\\\\d{9}$")) {
+ return numbers.replaceAll("(\\\\d{3})(\\\\d{3})(\\\\d{4})", "$1-$2-$3");
+ }
+
+ // ๊ธฐํ ์ง์ญ (11์๋ฆฌ: 0XX-XXXX-XXXX)
+ if (numbers.matches("^0\\\\d{10}$")) {
+ return numbers.replaceAll("(\\\\d{3})(\\\\d{4})(\\\\d{4})", "$1-$2-$3");
+ }
+
+ return phone; // ํฌ๋งทํ ์ ์์ผ๋ฉด ์๋ณธ ๋ฐํ
+ }
+
+ // ํ
์คํธ
+ public static void main(String[] args) {
+ System.out.println(formatPhone("01012345678")); // 010-1234-5678
+ System.out.println(formatPhone("0212345678")); // 02-1234-5678
+ System.out.println(formatPhone("0311234567")); // 031-123-4567
+ System.out.println(formatPhone("03112345678")); // 031-1234-5678
+ }
+}
+
+```
+
+### 1.2 ๋ฐ์ดํฐ ํ์ฑ (Parsing)
+
+### ๋ก๊ทธ ํ์ผ ํ์ฑ
+
+```java
+/**
+ * ๋ก๊ทธ ํ์ผ์์ ์ ๋ณด ์ถ์ถ
+ * ๋ก๊ทธ ํ์: [2025-11-18 23:43:21] [ERROR] [UserService] User login failed - IP: 192.168.1.100
+ */
+public class LogParser {
+
+ private static final Pattern LOG_PATTERN = Pattern.compile(
+ "\\\\[(\\\\d{4}-\\\\d{2}-\\\\d{2} \\\\d{2}:\\\\d{2}:\\\\d{2})\\\\]\\\\s+" + // ํ์์คํฌํ
+ "\\\\[(\\\\w+)\\\\]\\\\s+" + // ๋ก๊ทธ ๋ ๋ฒจ
+ "\\\\[([\\\\w]+)\\\\]\\\\s+" + // ์๋น์ค๋ช
+ "(.+?)(?:\\\\s+-\\\\s+IP:\\\\s+(\\\\d+\\\\.\\\\d+\\\\.\\\\d+\\\\.\\\\d+))?" // ๋ฉ์์ง ๋ฐ IP (์ต์
)
+ );
+
+ public static class LogEntry {
+ String timestamp;
+ String level;
+ String service;
+ String message;
+ String ipAddress;
+
+ @Override
+ public String toString() {
+ return String.format("LogEntry{time='%s', level='%s', service='%s', message='%s', ip='%s'}",
+ timestamp, level, service, message, ipAddress);
+ }
+ }
+
+ public static LogEntry parse(String logLine) {
+ Matcher m = LOG_PATTERN.matcher(logLine);
+ if (!m.find()) {
+ return null;
+ }
+
+ LogEntry entry = new LogEntry();
+ entry.timestamp = m.group(1);
+ entry.level = m.group(2);
+ entry.service = m.group(3);
+ entry.message = m.group(4);
+ entry.ipAddress = m.group(5); // null์ผ ์ ์์
+
+ return entry;
+ }
+
+ // ํน์ IP ์ฃผ์ ์ถ์ถ
+ public static List extractIPs(String text) {
+ List ips = new ArrayList<>();
+ Pattern ipPattern = Pattern.compile("\\\\b(?:\\\\d{1,3}\\\\.){3}\\\\d{1,3}\\\\b");
+ Matcher m = ipPattern.matcher(text);
+
+ while (m.find()) {
+ ips.add(m.group());
+ }
+
+ return ips;
+ }
+
+ // ์๋ฌ ๋ก๊ทธ๋ง ํํฐ๋ง
+ public static List filterErrorLogs(List logs) {
+ return logs.stream()
+ .filter(log -> log.matches(".*\\\\[ERROR\\\\].*"))
+ .collect(Collectors.toList());
+ }
+
+ // ํ
์คํธ
+ public static void main(String[] args) {
+ String log1 = "[2025-11-18 23:43:21] [ERROR] [UserService] User login failed - IP: 192.168.1.100";
+ String log2 = "[2025-11-18 23:43:22] [INFO] [PaymentService] Payment processed successfully";
+
+ LogEntry entry1 = parse(log1);
+ LogEntry entry2 = parse(log2);
+
+ System.out.println(entry1);
+ System.out.println(entry2);
+
+ // IP ์ถ์ถ
+ String text = "Connections from 192.168.1.1, 10.0.0.1, and 172.16.0.1";
+ List ips = extractIPs(text);
+ System.out.println("์ถ์ถ๋ IP: " + ips);
+ }
+}
+
+```
+
+### URL ํ์ฑ
+
+```java
+public class URLParser {
+
+ private static final Pattern URL_PATTERN = Pattern.compile(
+ "^(?https?)://" + // ํ๋กํ ์ฝ
+ "(?[^:/]+)" + // ๋๋ฉ์ธ
+ "(?::(?\\\\d+))?" + // ํฌํธ (์ต์
)
+ "(?/[^?#]*)?" + // ๊ฒฝ๋ก (์ต์
)
+ "(?:\\\\?(?[^#]*))?" + // ์ฟผ๋ฆฌ ์คํธ๋ง (์ต์
)
+ "(?:#(?.*))?$" // ํ๋๊ทธ๋จผํธ (์ต์
)
+ );
+
+ public static class URLInfo {
+ String protocol;
+ String domain;
+ String port;
+ String path;
+ String query;
+ String fragment;
+ Map queryParams;
+
+ @Override
+ public String toString() {
+ return String.format("URL{protocol='%s', domain='%s', port='%s', path='%s', query='%s', fragment='%s', params=%s}",
+ protocol, domain, port, path, query, fragment, queryParams);
+ }
+ }
+
+ public static URLInfo parse(String url) {
+ Matcher m = URL_PATTERN.matcher(url);
+ if (!m.matches()) {
+ return null;
+ }
+
+ URLInfo info = new URLInfo();
+ info.protocol = m.group("protocol");
+ info.domain = m.group("domain");
+ info.port = m.group("port");
+ info.path = m.group("path");
+ info.query = m.group("query");
+ info.fragment = m.group("fragment");
+
+ // ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ ํ์ฑ
+ if (info.query != null && !info.query.isEmpty()) {
+ info.queryParams = parseQueryString(info.query);
+ }
+
+ return info;
+ }
+
+ private static Map parseQueryString(String query) {
+ Map params = new HashMap<>();
+ Pattern paramPattern = Pattern.compile("([^&=]+)=([^&]*)");
+ Matcher m = paramPattern.matcher(query);
+
+ while (m.find()) {
+ params.put(m.group(1), m.group(2));
+ }
+
+ return params;
+ }
+
+ // ํ
์คํธ
+ public static void main(String[] args) {
+ String url = "";
+ URLInfo info = parse(url);
+ System.out.println(info);
+
+ // ์ถ๋ ฅ:
+ // URL{protocol='https', domain='example.com', port='8080',
+ // path='/api/users', query='id=123&name=john',
+ // fragment='section1', params={id=123, name=john}}
+ }
+}
+
+```
+
+### CSV/TSV ํ์ฑ
+
+```java
+public class CSVParser {
+
+ // ๊ธฐ๋ณธ CSV (์ผํ๋ก ๊ตฌ๋ถ, ๋ฐ์ดํ ์ฒ๋ฆฌ)
+ private static final Pattern CSV_PATTERN = Pattern.compile(
+ ",(?=(?:[^\\"]*\\"[^\\"]*\\")*[^\\"]*$)" // ๋ฐ์ดํ ์ธ๋ถ์ ์ผํ๋ง ๋งค์นญ
+ );
+
+ // RFC 4180 ์ค์ CSV ํ์
+ public static List parseCSVLine(String line) {
+ List fields = new ArrayList<>();
+
+ // ๋ณต์กํ ์ผ์ด์ค ์ฒ๋ฆฌ: "field1","field with, comma","field with ""quotes"""
+ Pattern pattern = Pattern.compile(
+ "\\"([^\\"]*(?:\\"\\"[^\\"]*)*)\\"|([^,]+)|(?<=,)(?=,)|^(?=,)|(?<=,)$"
+ );
+
+ Matcher m = pattern.matcher(line);
+ while (m.find()) {
+ String field;
+ if (m.group(1) != null) {
+ // ๋ฐ์ดํ๋ก ๊ฐ์ธ์ง ํ๋
+ field = m.group(1).replace("\\"\\"", "\\"");
+ } else if (m.group(2) != null) {
+ // ์ผ๋ฐ ํ๋
+ field = m.group(2);
+ } else {
+ // ๋น ํ๋
+ field = "";
+ }
+ fields.add(field);
+ }
+
+ return fields;
+ }
+
+ // ๊ฐ๋จํ ๋ฒ์ (๋ฐ์ดํ ์๋ ๊ฒฝ์ฐ)
+ public static List parseSimpleCSV(String line) {
+ return Arrays.asList(line.split(","));
+ }
+
+ // ํ
์คํธ
+ public static void main(String[] args) {
+ // ๋ณต์กํ ์ผ์ด์ค
+ String line1 = "John,Doe,\\"123 Main St, Apt 4\\",\\"He said \\"\\"Hello\\"\\"\\"";
+ List fields1 = parseCSVLine(line1);
+ System.out.println(fields1);
+ // [John, Doe, 123 Main St, Apt 4, He said "Hello"]
+
+ // ๊ฐ๋จํ ์ผ์ด์ค
+ String line2 = "apple,banana,cherry";
+ List fields2 = parseSimpleCSV(line2);
+ System.out.println(fields2);
+ // [apple, banana, cherry]
+ }
+}
+
+```
+
+---
+
+## ๐ง 2. Spring Boot ์ค๋ฌด ํตํฉ ์์
+
+### 2.1 Validation with Bean Validation
+
+```java
+import jakarta.validation.Constraint;
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+import jakarta.validation.Payload;
+import java.lang.annotation.*;
+
+// ์ปค์คํ
์ด๋
ธํ
์ด์
์ ์
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = PhoneNumberValidator.class)
+@Documented
+public @interface PhoneNumber {
+ String message() default "์ ํจํ์ง ์์ ์ ํ๋ฒํธ ํ์์
๋๋ค";
+ Class>[] groups() default {};
+ Class extends Payload>[] payload() default {};
+}
+
+// Validator ๊ตฌํ
+public class PhoneNumberValidator implements ConstraintValidator {
+
+ private static final String PHONE_PATTERN = "^(010-\\\\d{4}-\\\\d{4}|0\\\\d{1,2}-\\\\d{3,4}-\\\\d{4})$";
+
+ @Override
+ public boolean isValid(String value, ConstraintValidatorContext context) {
+ if (value == null || value.isEmpty()) {
+ return true; // @NotNull๊ณผ ํจ๊ป ์ฌ์ฉ
+ }
+ return value.matches(PHONE_PATTERN);
+ }
+}
+
+// DTO ์ฌ์ฉ
+public class UserRegistrationDTO {
+
+ @NotBlank(message = "์ด๋ฆ์ ํ์์
๋๋ค")
+ @Size(min = 2, max = 50, message = "์ด๋ฆ์ 2-50์์ฌ์ผ ํฉ๋๋ค")
+ private String name;
+
+ @NotBlank(message = "์ด๋ฉ์ผ์ ํ์์
๋๋ค")
+ @Email(message = "์ ํจํ ์ด๋ฉ์ผ ์ฃผ์๋ฅผ ์
๋ ฅํ์ธ์")
+ private String email;
+
+ @NotBlank(message = "์ ํ๋ฒํธ๋ ํ์์
๋๋ค")
+ @PhoneNumber // ์ปค์คํ
๊ฒ์ฆ
+ private String phone;
+
+ @NotBlank(message = "๋น๋ฐ๋ฒํธ๋ ํ์์
๋๋ค")
+ @Pattern(
+ regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\\\d)(?=.*[@$!%*?&])[A-Za-z\\\\d@$!%*?&]{8,20}$",
+ message = "๋น๋ฐ๋ฒํธ๋ 8-20์์ด๋ฉฐ, ๋์๋ฌธ์, ์ซ์, ํน์๋ฌธ์๋ฅผ ๊ฐ๊ฐ 1๊ฐ ์ด์ ํฌํจํด์ผ ํฉ๋๋ค"
+ )
+ private String password;
+
+ // getters, setters...
+}
+
+// Controller
+@RestController
+@RequestMapping("/api/users")
+public class UserController {
+
+ @PostMapping("/register")
+ public ResponseEntity> register(@Valid @RequestBody UserRegistrationDTO dto) {
+ // @Valid๊ฐ ์๋์ผ๋ก ๊ฒ์ฆ ์ํ
+ // ๊ฒ์ฆ ์คํจ ์ MethodArgumentNotValidException ๋ฐ์
+
+ // ๊ฒ์ฆ ํต๊ณผ ํ ๋ก์ง
+ return ResponseEntity.ok("๋ฑ๋ก ์ฑ๊ณต");
+ }
+}
+
+// Exception Handler
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public ResponseEntity