| Matthias Andreas Benkard | 7d46f07 | 2026-08-24 21:43:05 +0200 | [diff] [blame] | 1 | // SPDX-FileCopyrightText: 2026 Matthias Andreas Benkard <code@mail.matthias.benkard.de> |
| Matthias Andreas Benkard | 279fca6 | 2026-08-23 08:32:08 +0200 | [diff] [blame] | 2 | // SPDX-License-Identifier: AGPL-3.0-or-later |
| Matthias Andreas Benkard | d1e2fac | 2026-07-13 07:09:04 +0200 | [diff] [blame] | 3 | package eu.mulk.aendggner.aenderung; |
| 4 | |
| 5 | import org.jspecify.annotations.Nullable; |
| 6 | |
| 7 | /** |
| 8 | * Ein einzelner Änderungsbefehl eines Änderungsgesetzes, wie er im Handbuch der Rechtsförmlichkeit |
| 9 | * vorgesehen ist. Nicht erkannte Befehle landen als {@link UnbekannterBefehl} im Ergebnis — sie |
| 10 | * werden nie stillschweigend verworfen. |
| 11 | */ |
| 12 | public sealed interface Aenderungsbefehl { |
| 13 | |
| 14 | /** Die betroffene Stelle im Stammgesetz. */ |
| 15 | Stelle stelle(); |
| 16 | |
| 17 | /** Herkunft des Befehls im Änderungsgesetz. */ |
| 18 | Provenienz provenienz(); |
| 19 | |
| 20 | /** Strukturelle Ebene eines eingefügten oder angefügten Elements. */ |
| 21 | enum Ebene { |
| 22 | PARAGRAPH, |
| 23 | ABSATZ, |
| 24 | SATZ, |
| Matthias Andreas Benkard | 20d8b08 | 2026-08-25 22:49:04 +0200 | [diff] [blame] | 25 | /** Die Hälfte eines am Strichpunkt geteilten Satzes; sie beginnt keinen neuen Satz. */ |
| 26 | HALBSATZ, |
| Matthias Andreas Benkard | d1e2fac | 2026-07-13 07:09:04 +0200 | [diff] [blame] | 27 | NUMMER, |
| 28 | BUCHSTABE |
| 29 | } |
| 30 | |
| 31 | /** Anker für Wörter-Einfügungen innerhalb eines Textbereichs. */ |
| 32 | sealed interface WortAnker { |
| 33 | record NachWoertern(String woerter) implements WortAnker {} |
| 34 | |
| 35 | record VorWoertern(String woerter) implements WortAnker {} |
| 36 | |
| 37 | record AmEnde() implements WortAnker {} |
| 38 | |
| 39 | record VorKommaAmEnde() implements WortAnker {} |
| 40 | } |
| 41 | |
| 42 | /** „… werden die Wörter „A“ durch die Wörter „B“ ersetzt.“ */ |
| 43 | record Ersetzung( |
| 44 | Stelle stelle, String alt, String neu, boolean jeweils, boolean amEnde, Provenienz provenienz) |
| Matthias Andreas Benkard | ff8e983 | 2026-08-25 22:40:05 +0200 | [diff] [blame] | 45 | implements Aenderungsbefehl { |
| 46 | public Ersetzung mitStelle(Stelle neueStelle) { |
| 47 | return new Ersetzung(neueStelle, alt, neu, jeweils, amEnde, provenienz); |
| 48 | } |
| 49 | } |
| Matthias Andreas Benkard | d1e2fac | 2026-07-13 07:09:04 +0200 | [diff] [blame] | 50 | |
| 51 | /** „§ X wird wie folgt gefasst: „…““ */ |
| 52 | record Neufassung(Stelle stelle, String neuerText, Provenienz provenienz) |
| 53 | implements Aenderungsbefehl {} |
| 54 | |
| Matthias Andreas Benkard | 8ae4972 | 2026-07-13 21:32:53 +0200 | [diff] [blame] | 55 | /** |
| 56 | * „§ 2 Absatz 2 wird durch die folgenden Absätze 2 und 3 ersetzt: „…““ — ein Ziel wird durch |
| 57 | * einen Block ersetzt, der auch mehrere neue Einheiten enthalten darf. |
| Matthias Andreas Benkard | ebd5a7b | 2026-07-15 20:54:27 +0200 | [diff] [blame] | 58 | * |
| 59 | * @param stelle das (erste) zu ersetzende Ziel. |
| 60 | * @param bisStelle bei einem zusammenhängenden Bereich („Die Absätze 8 und 9 …“, „Die Sätze 4 bis |
| 61 | * 5 …“) das letzte Ziel; {@code null} bei einem Einzelziel. |
| Matthias Andreas Benkard | 8ae4972 | 2026-07-13 21:32:53 +0200 | [diff] [blame] | 62 | */ |
| Matthias Andreas Benkard | ebd5a7b | 2026-07-15 20:54:27 +0200 | [diff] [blame] | 63 | record StrukturErsetzung( |
| 64 | Stelle stelle, @Nullable Stelle bisStelle, Ebene ebene, String text, Provenienz provenienz) |
| 65 | implements Aenderungsbefehl { |
| 66 | |
| 67 | /** Einzelziel-Konstruktor (kein Bereich). */ |
| 68 | public StrukturErsetzung(Stelle stelle, Ebene ebene, String text, Provenienz provenienz) { |
| 69 | this(stelle, null, ebene, text, provenienz); |
| 70 | } |
| 71 | } |
| Matthias Andreas Benkard | 8ae4972 | 2026-07-13 21:32:53 +0200 | [diff] [blame] | 72 | |
| Matthias Andreas Benkard | d1e2fac | 2026-07-13 07:09:04 +0200 | [diff] [blame] | 73 | /** „… werden nach dem Wort „A“ die Wörter „B“ eingefügt.“ */ |
| 74 | record WoerterEinfuegung(Stelle stelle, WortAnker anker, String woerter, Provenienz provenienz) |
| Matthias Andreas Benkard | ff8e983 | 2026-08-25 22:40:05 +0200 | [diff] [blame] | 75 | implements Aenderungsbefehl { |
| 76 | public WoerterEinfuegung mitStelle(Stelle neueStelle) { |
| 77 | return new WoerterEinfuegung(neueStelle, anker, woerter, provenienz); |
| 78 | } |
| 79 | } |
| Matthias Andreas Benkard | d1e2fac | 2026-07-13 07:09:04 +0200 | [diff] [blame] | 80 | |
| 81 | /** |
| 82 | * „Nach § 28 wird folgender § 28a eingefügt: „…““ |
| 83 | * |
| 84 | * @param stelle die Ankerstelle, relativ zu der eingefügt wird. |
| 85 | * @param vorher {@code true} bei „Vor …“, {@code false} bei „Nach …“. |
| 86 | * @param bezeichnung die Bezeichnung des neuen Elements (z.B. „28a“, „5a“); {@code null}, wenn |
| 87 | * der Befehl keine nennt (z.B. „folgender Satz“). |
| Matthias Andreas Benkard | 5700bbc | 2026-08-15 08:36:14 +0200 | [diff] [blame] | 88 | * @param anker bei „Vor den Wörtern „Aus dem Bereich Verkehr:“ wird folgender Absatz 5 eingefügt“ |
| 89 | * der Wortanker, der die Position innerhalb von {@code stelle} bestimmt; {@code null}, wenn |
| 90 | * die Position wie üblich aus der Struktur folgt. |
| Matthias Andreas Benkard | d1e2fac | 2026-07-13 07:09:04 +0200 | [diff] [blame] | 91 | */ |
| 92 | record StrukturEinfuegung( |
| 93 | Stelle stelle, |
| 94 | boolean vorher, |
| 95 | Ebene ebene, |
| 96 | @Nullable String bezeichnung, |
| 97 | String text, |
| Matthias Andreas Benkard | 5f30f56 | 2026-07-28 06:00:16 +0200 | [diff] [blame] | 98 | @Nullable WortAnker anker, |
| Matthias Andreas Benkard | d1e2fac | 2026-07-13 07:09:04 +0200 | [diff] [blame] | 99 | Provenienz provenienz) |
| Matthias Andreas Benkard | 5f30f56 | 2026-07-28 06:00:16 +0200 | [diff] [blame] | 100 | implements Aenderungsbefehl { |
| 101 | |
| 102 | /** Konstruktor für die strukturbestimmte Position (kein Wortanker). */ |
| 103 | public StrukturEinfuegung( |
| 104 | Stelle stelle, |
| 105 | boolean vorher, |
| 106 | Ebene ebene, |
| 107 | @Nullable String bezeichnung, |
| 108 | String text, |
| 109 | Provenienz provenienz) { |
| 110 | this(stelle, vorher, ebene, bezeichnung, text, null, provenienz); |
| 111 | } |
| 112 | } |
| Matthias Andreas Benkard | d1e2fac | 2026-07-13 07:09:04 +0200 | [diff] [blame] | 113 | |
| 114 | /** „Folgender Absatz 9 wird angefügt: „…““ / „Dem Absatz 3 wird folgender Satz angefügt: …“ */ |
| 115 | record Anfuegung( |
| 116 | Stelle stelle, Ebene ebene, @Nullable String bezeichnung, String text, Provenienz provenienz) |
| 117 | implements Aenderungsbefehl {} |
| 118 | |
| 119 | /** „§ X / Absatz Y wird aufgehoben.“ */ |
| 120 | record Aufhebung(Stelle stelle, Provenienz provenienz) implements Aenderungsbefehl {} |
| 121 | |
| 122 | /** „… werden die Wörter „A“ gestrichen.“ */ |
| 123 | record Streichung(Stelle stelle, String woerter, Provenienz provenienz) |
| Matthias Andreas Benkard | ff8e983 | 2026-08-25 22:40:05 +0200 | [diff] [blame] | 124 | implements Aenderungsbefehl { |
| 125 | public Streichung mitStelle(Stelle neueStelle) { |
| 126 | return new Streichung(neueStelle, woerter, provenienz); |
| 127 | } |
| 128 | } |
| Matthias Andreas Benkard | d1e2fac | 2026-07-13 07:09:04 +0200 | [diff] [blame] | 129 | |
| 130 | /** „Der bisherige Absatz 2 wird Absatz 3.“ */ |
| 131 | record Umnummerierung(Stelle stelle, Stelle neu, Provenienz provenienz) |
| 132 | implements Aenderungsbefehl {} |
| 133 | |
| Matthias Andreas Benkard | b05e44a | 2026-07-13 21:33:20 +0200 | [diff] [blame] | 134 | /** |
| Matthias Andreas Benkard | 08c2747 | 2026-08-22 19:02:46 +0200 | [diff] [blame] | 135 | * „Die bisherigen §§ 9 bis 12 werden die §§ 8 bis 10.“ — Umnummerierung eines Bereichs, dessen |
| 136 | * Zuordnung erst am Gesetz ablesbar ist. |
| 137 | * |
| 138 | * <p>Der Befehl nennt <em>Bezeichnungen</em>, gemeint sind aber <em>Einheiten</em>: Trägt der |
| 139 | * Ausgangsbereich einen aufgehobenen Platzhalter, so zählt der Verordnungsgeber ihn nicht mit — |
| 140 | * er trägt keinen Inhalt, den man umnummerieren könnte. Deshalb bleibt dieser Befehl bis zur |
| 141 | * Anwendung ungeteilt; erst dort werden die vorhandenen Einheiten des Bereichs der Reihe nach den |
| 142 | * neuen Bezeichnungen zugeordnet (siehe {@code BefehlAnwender}). Geht die Zählung auch dann nicht |
| 143 | * auf, bleibt er manuell zu prüfen. |
| 144 | */ |
| 145 | record BereichsUmnummerierung( |
| 146 | Stelle stelle, Stelle bis, Stelle neu, Stelle neuBis, Provenienz provenienz) |
| 147 | implements Aenderungsbefehl {} |
| 148 | |
| 149 | /** |
| Matthias Andreas Benkard | 9a29b38 | 2026-07-13 21:33:43 +0200 | [diff] [blame] | 150 | * „Der Wortlaut wird Absatz 1.“ — der bisher unnummerierte Normtext erhält die Absatznummer, wird |
| 151 | * also zum ersten Absatz (Vorbereitung für das Anfügen weiterer Absätze). |
| 152 | */ |
| 153 | record WortlautZuAbsatz(Stelle stelle, String nummer, Provenienz provenienz) |
| 154 | implements Aenderungsbefehl {} |
| 155 | |
| 156 | /** |
| Matthias Andreas Benkard | 65eff08 | 2026-07-18 12:24:32 +0200 | [diff] [blame] | 157 | * „Der Wortlaut wird Satz 1.“ (bayerisches Landesrecht) — der bisher unnummerierte Text erhält |
| 158 | * die amtliche Satznummer als Superskript (Vorbereitung für das Anfügen weiterer Sätze). |
| 159 | */ |
| 160 | record WortlautZuSatz(Stelle stelle, String nummer, Provenienz provenienz) |
| 161 | implements Aenderungsbefehl {} |
| 162 | |
| 163 | /** |
| 164 | * „Dem Wortlaut werden die folgenden Abs. 1 bis 4 vorangestellt: „…““ (bayerisches Landesrecht) — |
| 165 | * neue Absätze treten vor den bisherigen Normtext. |
| 166 | */ |
| 167 | record WortlautVoranstellung(Stelle stelle, String text, Provenienz provenienz) |
| 168 | implements Aenderungsbefehl {} |
| 169 | |
| 170 | /** |
| 171 | * „Fußnote 1 wird aufgehoben.“ / „Die Fußnoten 9 und 10 werden aufgehoben.“ (bayerisches |
| 172 | * Landesrecht) — entfernt die Fußnotenzeile(n) „ⁿ) [Amtl. Anm.:] …“ samt der Inline-Marker „ⁿ)“ |
| 173 | * aus der Kontextnorm. |
| 174 | */ |
| 175 | record FussnotenAufhebung(Stelle stelle, java.util.List<String> nummern, Provenienz provenienz) |
| 176 | implements Aenderungsbefehl { |
| 177 | |
| 178 | public FussnotenAufhebung { |
| 179 | nummern = java.util.List.copyOf(nummern); |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * „In Satz 1 wird die Satznummerierung „1“ gestrichen.“ (bayerisches Landesrecht) — entfernt die |
| 185 | * amtliche Superskript-Satznummer am Anfang des bezeichneten Bereichs (etwa nachdem der zweite |
| 186 | * und letzte Satz eines Absatzes aufgehoben wurde). |
| 187 | */ |
| 188 | record SatznummerierungStreichung(Stelle stelle, String nummer, Provenienz provenienz) |
| 189 | implements Aenderungsbefehl {} |
| 190 | |
| 191 | /** |
| Matthias Andreas Benkard | b7e9f0f | 2026-07-16 21:12:08 +0200 | [diff] [blame] | 192 | * „Nach § 33 werden die folgenden Überschriften zu Teil 3 und zu Teil 3 Abschnitt 1 eingefügt: |
| 193 | * „…““ bzw. „Die bisherigen Überschriften zu Teil 4 und Teil 4 Abschnitt 1 werden durch die |
| 194 | * folgende Überschrift zu Abschnitt 2 ersetzt: „…““ — neue Gliederungs-Überschriften im |
| 195 | * Gliederungsbaum. |
| 196 | * |
| 197 | * @param stelle der Anker-§ („Nach § 33“); leer bei der Ersetzungsform. |
| 198 | * @param neue die neuen Einheiten in Zitatreihenfolge (Titel stehen im Zitat). |
| 199 | * @param ersetzte die zu ersetzenden bisherigen Einheiten (leer bei der Einfügeform); jede als |
| 200 | * Pfad („Teil 4 Abschnitt 1“ → [Teil 4, Abschnitt 1]). |
| 201 | * @param text das Zitat mit den Überschriften. |
| 202 | */ |
| 203 | record GliederungsUeberschriften( |
| 204 | Stelle stelle, |
| 205 | java.util.List<Stelle.Gliederungseinheit> neue, |
| 206 | java.util.List<java.util.List<Stelle.Gliederungseinheit>> ersetzte, |
| 207 | String text, |
| 208 | Provenienz provenienz) |
| 209 | implements Aenderungsbefehl { |
| 210 | |
| 211 | public GliederungsUeberschriften { |
| 212 | neue = java.util.List.copyOf(neue); |
| 213 | ersetzte = java.util.List.copyOf(ersetzte); |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | /** |
| Matthias Andreas Benkard | b05e44a | 2026-07-13 21:33:20 +0200 | [diff] [blame] | 218 | * „In A und B wird jeweils …“ — ein Befehl, der dieselbe Operation auf mehrere, per „und“ |
| 219 | * koordinierte Stellen anwendet. Die Teilbefehle teilen sich Provenienz und Befehlszeile; der |
| 220 | * Applier fasst sie zu einem Protokolleintrag zusammen. |
| 221 | */ |
| 222 | record Sammelbefehl(java.util.List<Aenderungsbefehl> teilbefehle) implements Aenderungsbefehl { |
| 223 | |
| 224 | public Sammelbefehl { |
| 225 | teilbefehle = java.util.List.copyOf(teilbefehle); |
| 226 | } |
| 227 | |
| 228 | @Override |
| 229 | public Stelle stelle() { |
| 230 | return teilbefehle.get(0).stelle(); |
| 231 | } |
| 232 | |
| 233 | @Override |
| 234 | public Provenienz provenienz() { |
| 235 | return teilbefehle.get(0).provenienz(); |
| 236 | } |
| 237 | } |
| 238 | |
| Matthias Andreas Benkard | d1e2fac | 2026-07-13 07:09:04 +0200 | [diff] [blame] | 239 | /** Fallback für alles, was der Parser nicht versteht — muss manuell geprüft werden. */ |
| 240 | record UnbekannterBefehl(Stelle stelle, String originalText, Provenienz provenienz) |
| 241 | implements Aenderungsbefehl {} |
| Matthias Andreas Benkard | a231681 | 2026-08-25 22:55:02 +0200 | [diff] [blame] | 242 | |
| 243 | /** |
| 244 | * „Die Inhaltsübersicht wird entsprechend der vorstehenden Nummer 8 Buchst. a geändert.“ |
| 245 | * |
| 246 | * <p>Ein Befehl, der seinen Inhalt nicht selbst nennt, sondern auf einen anderen Punkt desselben |
| 247 | * Artikels verweist. Er wird erkannt, aber nicht angewandt: Die sinngemäße Übertragung setzte |
| 248 | * voraus, dass der Anwender die Befehlsliste zurückliest und das Ziel umdeutet („Überschrift des |
| 249 | * § 13“ → Titelspalte der Übersichtszeile). Erkannt zu werden ist gleichwohl mehr als gar nichts |
| 250 | * — die Rüge sagt dann, dass das Erzeugnis die Grenze zieht, und nicht, dass die Vorlage |
| 251 | * unverständlich sei. |
| 252 | * |
| 253 | * @param verweis der Wortlaut der Verweisung („Nummer 8 Buchst. a“). |
| 254 | */ |
| 255 | record VerweisenderBefehl(Stelle stelle, String verweis, Provenienz provenienz) |
| 256 | implements Aenderungsbefehl {} |
| Matthias Andreas Benkard | d1e2fac | 2026-07-13 07:09:04 +0200 | [diff] [blame] | 257 | } |