blob: 3138eeb7bf8155f4a8fcb155d3c7ff06a086ea2a [file] [log] [blame]
Matthias Andreas Benkard7d46f072026-08-24 21:43:05 +02001// SPDX-FileCopyrightText: 2026 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
Matthias Andreas Benkard279fca62026-08-23 08:32:08 +02002// SPDX-License-Identifier: AGPL-3.0-or-later
Matthias Andreas Benkardd1e2fac2026-07-13 07:09:04 +02003package eu.mulk.aendggner.aenderung;
4
5import 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 */
12public 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 Benkard20d8b082026-08-25 22:49:04 +020025 /** Die Hälfte eines am Strichpunkt geteilten Satzes; sie beginnt keinen neuen Satz. */
26 HALBSATZ,
Matthias Andreas Benkardd1e2fac2026-07-13 07:09:04 +020027 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 Benkardff8e9832026-08-25 22:40:05 +020045 implements Aenderungsbefehl {
46 public Ersetzung mitStelle(Stelle neueStelle) {
47 return new Ersetzung(neueStelle, alt, neu, jeweils, amEnde, provenienz);
48 }
49 }
Matthias Andreas Benkardd1e2fac2026-07-13 07:09:04 +020050
51 /** „§ X wird wie folgt gefasst: „…““ */
52 record Neufassung(Stelle stelle, String neuerText, Provenienz provenienz)
53 implements Aenderungsbefehl {}
54
Matthias Andreas Benkard8ae49722026-07-13 21:32:53 +020055 /**
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 Benkardebd5a7b2026-07-15 20:54:27 +020058 *
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 Benkard8ae49722026-07-13 21:32:53 +020062 */
Matthias Andreas Benkardebd5a7b2026-07-15 20:54:27 +020063 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 Benkard8ae49722026-07-13 21:32:53 +020072
Matthias Andreas Benkardd1e2fac2026-07-13 07:09:04 +020073 /** „… werden nach dem Wort „A“ die Wörter „B“ eingefügt.“ */
74 record WoerterEinfuegung(Stelle stelle, WortAnker anker, String woerter, Provenienz provenienz)
Matthias Andreas Benkardff8e9832026-08-25 22:40:05 +020075 implements Aenderungsbefehl {
76 public WoerterEinfuegung mitStelle(Stelle neueStelle) {
77 return new WoerterEinfuegung(neueStelle, anker, woerter, provenienz);
78 }
79 }
Matthias Andreas Benkardd1e2fac2026-07-13 07:09:04 +020080
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 Benkard5700bbc2026-08-15 08:36:14 +020088 * @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 Benkardd1e2fac2026-07-13 07:09:04 +020091 */
92 record StrukturEinfuegung(
93 Stelle stelle,
94 boolean vorher,
95 Ebene ebene,
96 @Nullable String bezeichnung,
97 String text,
Matthias Andreas Benkard5f30f562026-07-28 06:00:16 +020098 @Nullable WortAnker anker,
Matthias Andreas Benkardd1e2fac2026-07-13 07:09:04 +020099 Provenienz provenienz)
Matthias Andreas Benkard5f30f562026-07-28 06:00:16 +0200100 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 Benkardd1e2fac2026-07-13 07:09:04 +0200113
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 Benkardff8e9832026-08-25 22:40:05 +0200124 implements Aenderungsbefehl {
125 public Streichung mitStelle(Stelle neueStelle) {
126 return new Streichung(neueStelle, woerter, provenienz);
127 }
128 }
Matthias Andreas Benkardd1e2fac2026-07-13 07:09:04 +0200129
130 /** „Der bisherige Absatz 2 wird Absatz 3.“ */
131 record Umnummerierung(Stelle stelle, Stelle neu, Provenienz provenienz)
132 implements Aenderungsbefehl {}
133
Matthias Andreas Benkardb05e44a2026-07-13 21:33:20 +0200134 /**
Matthias Andreas Benkard08c27472026-08-22 19:02:46 +0200135 * „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 Benkard9a29b382026-07-13 21:33:43 +0200150 * „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 Benkard65eff082026-07-18 12:24:32 +0200157 * „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 Benkardb7e9f0f2026-07-16 21:12:08 +0200192 * „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 Benkardb05e44a2026-07-13 21:33:20 +0200218 * „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 Benkardd1e2fac2026-07-13 07:09:04 +0200239 /** 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 Benkarda2316812026-08-25 22:55:02 +0200242
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 Benkardd1e2fac2026-07-13 07:09:04 +0200257}