# PRD – CSV→OutputImageFormat Parser (Erweiterung zum „PRD – Java‑native Bildkonvertierung“) **Ziel:** Robustes Parsen einer komma‑separierten Zeichenkette (CSV) in eine **ordnungserhaltende, doppeltfreie** `List` – **ohne** externe Abhängigkeiten. Unterstützt **Synonyme** (z. B. `jpg`→`JPEG`, `tif`→`TIFF`), führende Punkte (`.jpg`), **MIME‑Typen** (`image/jpeg`) und **ImageIO‑Formatnamen** (`jpeg`). Fehlerhafte Tokens führen wahlweise zu **Exception** oder werden „lenient“ ignoriert. > Dieses PRD ergänzt das bestehende Dokument „PRD – Java‑native Bildkonvertierung (Spring Boot, OSS)“ und liefert die Spezifikation für den CSV‑Parser, der z. B. REST‑Parameter `allowed` (CSV) in `List` transformiert. --- ## 1. Scope **In Scope** - Utility‑Klasse zum Parsen einer CSV‑Liste in `List`. - Case‑insensitive, Whitespace‑tolerant, führende `.` entfernen. - Synonym‑Mapping für verbreitete Varianten (jpg/jpeg, tif/tiff, jp2/j2k/jpeg2000, webp, mime‑typen etc.). - **Dedup** bei gleichzeitigem Erhalt der **Eingabereihenfolge**. - Zwei Modi: - **Strict:** Unbekannte Tokens → `IllegalArgumentException`. - **Lenient:** Unbekannte Tokens werden **ignoriert**. - Öffentliche Helper‑API für Single‑Token‑Lookup. **Out of Scope** - Validierung gegen tatsächlich installierte Writer/Reader (das übernimmt die Konvertierungsschicht). - Automatisches Nachladen von externen Plugins. --- ## 2. Nicht‑funktionale Anforderungen - **Zero‑Deps**: reine JDK‑Funktionen, keine Tika/Guava etc. - **Performance**: O(n) über Anzahl Tokens; Lookup über vorkompilierte `Map`. - **Threadsafety**: statische, unveränderliche Lookup‑Map (unmodifiable); Parser‑Methoden sind reentrant und thread‑safe. - **Internationalisierung**: nicht relevant (Tokens sind technische Kürzel/MIME‑Typen). --- ## 3. Domänenmodell / Abhängigkeiten - **`OutputImageFormat` (Enum)** aus der Haupt‑PRD: liefert `extension`, `mimeType`, `imageIoFormatName`, `name()`. - Synonyme werden **aus dem Enum** abgeleitet und um gebräuchliche Aliasse ergänzt: - `JPEG` ⇄ `jpg` / `jpeg` / `image/jpeg` - `TIFF` ⇄ `tif` / `tiff` / `image/tiff` - `PNG` ⇄ `png` / `image/png` - `BMP` ⇄ `bmp` / `image/bmp` - `GIF` ⇄ `gif` / `image/gif` - `WEBP` ⇄ `webp` / `image/webp` - `JP2` ⇄ `jp2` / `j2k` / `jpeg2000` / `image/jp2` > Hinweis: Falls einzelne Enum‑Werte (WEBP/JP2) im Projekt deaktiviert werden, bleibt das Mapping konsistent – Keys auf nicht existierende Enum‑Werte entfallen. --- ## 4. Öffentliche API ```java public final class ImageFormatParsers { /** Strict: unbekannte Tokens -> IllegalArgumentException */ public static List parseCsv(String csv); /** Lenient: unbekannte Tokens werden ignoriert */ public static List parseCsvLenient(String csv); /** Einzel‑Token‑Lookup (case‑insensitive, . entfernt, Synonyme & MIME) */ public static Optional lookup(String token); } ``` **Verhalten** - `null` oder leere/blank CSV → leere Liste. - Trennung: `,` (Komma). Whitespace wird getrimmt, führende `.` entfernt (z. B. `.jpg`). - Reihenfolge: Erstes Vorkommen gewinnt; weitere Duplikate werden ignoriert (LinkedHashSet‑Semantik). - **Strict**: Tokens, die keinem Format zugeordnet werden können, sammeln und am Ende `IllegalArgumentException` mit Auflistung werfen. - **Lenient**: Unbekannte Tokens stillschweigend überspringen. --- ## 5. Implementierungsdetails - Statische `Map LOOKUP` mit allen erlaubten Schlüsseln (kleingeschrieben): - `enum.name()` → `png`, `jpeg`, … (kleinbuchstabig) - `extension()` → `png`, `jpg`, `tiff`, … - `imageIoFormatName()` → typ. `png`, `jpeg`, `tiff` … - `mimeType()` → `image/png`, `image/jpeg`, … - **Zusatz‑Synonyme** je nach Enumwert (siehe §3). - `normalize(String)`: - `trim()`, `toLowerCase(Locale.ROOT)`, führenden Punkt entfernen. - Deduplizierung: `LinkedHashSet` → anschließend in `List.copyOf(...)`. **Beispiel (Strict)** ``` "PNG, JPEG, .tif, image/webp, png, jpg" → [PNG, JPEG, TIFF, WEBP] ``` --- ## 6. Teststrategie (JUnit 5) **Testklasse:** `ImageFormatParsersTest` **A. Positivfälle** 1. **Grundlegend**: `"PNG,JPEG"` → `[PNG, JPEG]` (Reihenfolge, Größe=2). 2. **Synonyme**: `"jpg,JPEG"` → `[JPEG]`; `"tif,TIFF"` → `[TIFF]`. 3. **MIME‑Tokens**: `"image/jpeg, image/tiff"` → `[JPEG, TIFF]`. 4. **ImageIO‑Formatname**: `"jpeg,tiff"` → `[JPEG, TIFF]`. 5. **Führender Punkt**: `".jpg, .tif"` → `[JPEG, TIFF]`. 6. **Whitespace & Case**: `" PnG , jPeG "` → `[PNG, JPEG]`. 7. **Dedup & Reihenfolge**: `"PNG, JPEG, png, jpeg"` → `[PNG, JPEG]`; `"jpeg,png,jpeg"` → `[JPEG, PNG]`. 8. **JP2‑Aliasse** (falls Enum enthält): `"j2k, jpeg2000, JP2"` → `[JP2]`. **B. Strict‑Fehlerfälle** 9. **Unbekanntes Token**: `"foo,PNG"` → `IllegalArgumentException` mit Hinweis auf `foo`. 10. **Nur Unbekannt**: `"foo,bar"` → `IllegalArgumentException` (Liste aller unbekannten Tokens). **C. Lenient‑Variante** 11. `"foo, PNG, bar"` → `[PNG]` (keine Exception). **D. Kantenfälle** 12. **Leer/Null**: `null` → `[]`, `" "` → `[]`. 13. **Doppelte Kommas**: `","` bzw. `"PNG,,JPEG"` → `[PNG, JPEG]`. **Hilfen** - Parametrisierte Tests (`@ParameterizedTest`) für Synonym‑Mengen. - AssertJ/JUnit Assertions für Reihenfolge & Inhalt. --- ## 7. Integration in die bestehende Lösung - REST‑Controller: `allowed` (CSV) → `ImageFormatParsers.parseCsv(...)`; `preferred` (String) → `lookup(...)` + Fehler, wenn `preferred ∉ allowed`. - `ImageFormatNegotiatorService`: Die geparste `allowedTargets`‑Liste und `preferredTarget` direkt übergeben. - Konfiguration: Optionale Default‑Liste in `application.yml` (z. B. `images.allowed-default: "PNG,JPEG"`) – beim Booten parsen. --- ## 8. Akzeptanzkriterien (DoD) - [ ] `parseCsv` (strict) wirft bei unbekannten Tokens eine `IllegalArgumentException` mit Token‑Liste. - [ ] `parseCsvLenient` ignoriert unbekannte Tokens und gibt bekannte in Eingabereihenfolge (dedupliziert) zurück. - [ ] Unterstützt Synonyme inkl. führender `.` und MIME‑Strings. - [ ] Testabdeckung ≥ 90 % für `ImageFormatParsers` (Branches: strict/lenient, Synonyme, Kantenfälle). - [ ] Thread‑safety gegeben (LOOKUP unveränderlich, Methoden reentrant). --- ## 9. Aufgabenliste (für Codex) 1. Klasse `ImageFormatParsers` anlegen, `LOOKUP` aus `OutputImageFormat` ableiten und Synonyme ergänzen. 2. Methoden `parseCsv`, `parseCsvLenient`, `lookup` implementieren. 3. Testklasse `ImageFormatParsersTest` mit den o. g. Fällen (JUnit 5, AssertJ optional) implementieren. 4. Beispiel‑Snippet in README/PRD (Verwendung aus REST‑Controller) ergänzen. --- ## 10. Beispiel‑Snippets **Verwendung (REST)** ```java List allowed = ImageFormatParsers.parseCsv(params.getAllowed()); OutputImageFormat preferred = ImageFormatParsers.lookup(params.getPreferred()) .orElseThrow(() -> new IllegalArgumentException("Unsupported preferred format")); if (!allowed.contains(preferred)) { throw new IllegalArgumentException("Preferred must be included in allowed"); } ``` **Lenient‑Konfiguration** ```java List defaults = ImageFormatParsers.parseCsvLenient(env.getProperty("images.allowed-default")); ``` --- ## 11. Paket & Dateistruktur (Vorschlag) ``` src/main/java/com/example/images/ OutputImageFormat.java ImageFormatParsers.java ImageConversionService.java ImageFormatNegotiatorService.java src/test/java/com/example/images/ ImageFormatParsersTest.java ``` --- ## 12. Risiken & Gegenmaßnahmen - **Token‑Drift**: Neue Enum‑Werte → Lookup beim Start aus Enum ableiten, Zusatz‑Synonyme zentral. - **Fehlkonfiguration**: `preferred ∉ allowed` → Validierung in REST/Service‑Schicht. - **Uneinheitliche Schreibweisen**: Abgefangen durch Normalisierung (trim, lowercase, Strip leading `.`). — Ende —