image-converter/prd/OutputImageFormat-Parser.md
2025-10-26 09:44:48 +01:00

171 lines
7.9 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PRD CSV→OutputImageFormat Parser (Erweiterung zum „PRD Javanative Bildkonvertierung“)
**Ziel:** Robustes Parsen einer kommaseparierten Zeichenkette (CSV) in eine **ordnungserhaltende, doppeltfreie** `List<OutputImageFormat>` **ohne** externe Abhängigkeiten. Unterstützt **Synonyme** (z.B. `jpg``JPEG`, `tif``TIFF`), führende Punkte (`.jpg`), **MIMETypen** (`image/jpeg`) und **ImageIOFormatnamen** (`jpeg`). Fehlerhafte Tokens führen wahlweise zu **Exception** oder werden „lenient“ ignoriert.
> Dieses PRD ergänzt das bestehende Dokument „PRD Javanative Bildkonvertierung (Spring Boot, OSS)“ und liefert die Spezifikation für den CSVParser, der z.B. RESTParameter `allowed` (CSV) in `List<OutputImageFormat>` transformiert.
---
## 1. Scope
**In Scope**
- UtilityKlasse zum Parsen einer CSVListe in `List<OutputImageFormat>`.
- Caseinsensitive, Whitespacetolerant, führende `.` entfernen.
- SynonymMapping für verbreitete Varianten (jpg/jpeg, tif/tiff, jp2/j2k/jpeg2000, webp, mimetypen etc.).
- **Dedup** bei gleichzeitigem Erhalt der **Eingabereihenfolge**.
- Zwei Modi:
- **Strict:** Unbekannte Tokens → `IllegalArgumentException`.
- **Lenient:** Unbekannte Tokens werden **ignoriert**.
- Öffentliche HelperAPI für SingleTokenLookup.
**Out of Scope**
- Validierung gegen tatsächlich installierte Writer/Reader (das übernimmt die Konvertierungsschicht).
- Automatisches Nachladen von externen Plugins.
---
## 2. Nichtfunktionale Anforderungen
- **ZeroDeps**: reine JDKFunktionen, keine Tika/Guava etc.
- **Performance**: O(n) über Anzahl Tokens; Lookup über vorkompilierte `Map`.
- **Threadsafety**: statische, unveränderliche LookupMap (unmodifiable); ParserMethoden sind reentrant und threadsafe.
- **Internationalisierung**: nicht relevant (Tokens sind technische Kürzel/MIMETypen).
---
## 3. Domänenmodell / Abhängigkeiten
- **`OutputImageFormat` (Enum)** aus der HauptPRD: 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 EnumWerte (WEBP/JP2) im Projekt deaktiviert werden, bleibt das Mapping konsistent Keys auf nicht existierende EnumWerte entfallen.
---
## 4. Öffentliche API
```java
public final class ImageFormatParsers {
/** Strict: unbekannte Tokens -> IllegalArgumentException */
public static List<OutputImageFormat> parseCsv(String csv);
/** Lenient: unbekannte Tokens werden ignoriert */
public static List<OutputImageFormat> parseCsvLenient(String csv);
/** EinzelTokenLookup (caseinsensitive, . entfernt, Synonyme & MIME) */
public static Optional<OutputImageFormat> 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 (LinkedHashSetSemantik).
- **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<String, OutputImageFormat> 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`, …
- **ZusatzSynonyme** je nach Enumwert (siehe §3).
- `normalize(String)`:
- `trim()`, `toLowerCase(Locale.ROOT)`, führenden Punkt entfernen.
- Deduplizierung: `LinkedHashSet<OutputImageFormat>` → 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. **MIMETokens**: `"image/jpeg, image/tiff"``[JPEG, TIFF]`.
4. **ImageIOFormatname**: `"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. **JP2Aliasse** (falls Enum enthält): `"j2k, jpeg2000, JP2"``[JP2]`.
**B. StrictFehlerfälle**
9. **Unbekanntes Token**: `"foo,PNG"``IllegalArgumentException` mit Hinweis auf `foo`.
10. **Nur Unbekannt**: `"foo,bar"``IllegalArgumentException` (Liste aller unbekannten Tokens).
**C. LenientVariante**
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 SynonymMengen.
- AssertJ/JUnit Assertions für Reihenfolge & Inhalt.
---
## 7. Integration in die bestehende Lösung
- RESTController: `allowed` (CSV) → `ImageFormatParsers.parseCsv(...)`; `preferred` (String) → `lookup(...)` + Fehler, wenn `preferred ∉ allowed`.
- `ImageFormatNegotiatorService`: Die geparste `allowedTargets`Liste und `preferredTarget` direkt übergeben.
- Konfiguration: Optionale DefaultListe 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 TokenListe.
- [ ] `parseCsvLenient` ignoriert unbekannte Tokens und gibt bekannte in Eingabereihenfolge (dedupliziert) zurück.
- [ ] Unterstützt Synonyme inkl. führender `.` und MIMEStrings.
- [ ] Testabdeckung ≥ 90% für `ImageFormatParsers` (Branches: strict/lenient, Synonyme, Kantenfälle).
- [ ] Threadsafety 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. BeispielSnippet in README/PRD (Verwendung aus RESTController) ergänzen.
---
## 10. BeispielSnippets
**Verwendung (REST)**
```java
List<OutputImageFormat> 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");
}
```
**LenientKonfiguration**
```java
List<OutputImageFormat> 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
- **TokenDrift**: Neue EnumWerte → Lookup beim Start aus Enum ableiten, ZusatzSynonyme zentral.
- **Fehlkonfiguration**: `preferred ∉ allowed` → Validierung in REST/ServiceSchicht.
- **Uneinheitliche Schreibweisen**: Abgefangen durch Normalisierung (trim, lowercase, Strip leading `.`).
— Ende —