All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.svws_nrw.asd.validate.ValidatorManager Maven / Gradle / Ivy

Go to download

Diese Bibliothek stellt grundlegende Datenypen und Algorithmen für die Prüfung der amtlichen Schuldaten in NRW bereit

There is a newer version: 1.0.0
Show newest version
package de.svws_nrw.asd.validate;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import de.svws_nrw.asd.adt.PairNN;
import de.svws_nrw.asd.data.CoreTypeData;
import de.svws_nrw.asd.data.CoreTypeException;
import de.svws_nrw.asd.types.schule.Schulform;
import jakarta.validation.constraints.NotNull;

/**
 * Ein Manager, um die Validatoren zu verwalten. Die Fehlerart-Kontexte
 * der Validatoren werden aus einer JSON-Datei geladen und entsprechende
 * Methoden zur Abfrage und Änderung bereit gestellt.
 */
public final class ValidatorManager {

	/** Die Version der Fehlerart-Kontexte */
	private static long _version;

	/** Die Fehlerart-Kontexte für jeden Validator als Historienliste */
	private static @NotNull Map> _data;

	/** Die ValidatorManager pro Schulform für den SVWS-Kontext */
	private static @NotNull Map _managerSVWS = new HashMap<>();

	/** Die ValidatorManager pro Schulform für deb Zebras-Kontext */
	private static @NotNull Map _managerZebras = new HashMap<>();

	/** Die Schulform, für den der ValidatorManager gilt */
	private final @NotNull Schulform _schulform;

	/** Die Umgebung, für den der ValidatorManager erzeugt wurde: true = ZeBrAS ; false = SVWS */
	private final boolean _isZebras;


	/* ----- Die nachfolgenden Attribute werden nicht initialisiert und werden als Cache verwendet, um z.B. den Schuljahres-bezogenen Zugriff zu cachen ----- */

	/** Eine geschachtelte Map, die einem Schuljar eine Map mit der Zuordnung der Validatoren zu ihrer Fehlerart für die Schulform _schulform */
	private final @NotNull HashMap> _mapSchuljahrValidatornameToFehlerart = new HashMap<>();

	/** Eine geschachtelte Map, die einem Schuljahr eine Map mit der Zuordnung einer Fehlerart zu jedem Validator für die Schulform _schulform */
	private final @NotNull HashMap>> _mapSchuljahrFehlerartToValidatorname = new HashMap<>();


	/**
	 * Erstellt einen neuen Manager für die übergebene Schulform und die Entsprechene Validierungsumgebung
	 * (Zebras oder SVWS)
	 *
	 * @param zebras            die Umgebung, in der gerade validiert wird: true: ZeBrAS  false: SVWS
	 * @param schulform			die Schulform, für die gerade
	 */
	private ValidatorManager(final @NotNull Schulform schulform, final boolean zebras) {
		this._schulform = schulform;
		this._isZebras = zebras;
	}


	/**
	 * Initialisierung des Validators mit den Daten, die aus einem json eingelesen wurden.
	 *
	 * @param version	Die Versionsnummer der Daten zu den Fehlerart-Kontexten.
	 * @param data		Die aus der JSON-Datei eingelesenen Daten.
	 */
	public static void init(final long version, @NotNull final Map> data) {
		_version = version;
		_data = data;
		_managerSVWS = new HashMap<>();
		_managerZebras = new HashMap<>();

		// TODO Überprüfung ob alle Validatoren in der Json aufgeführt sind

		// Überprüfung ob die Schulformen, die eingetragen sind, zu dem entsprechenden Zeitpunkt beim Core-Type überhaupt gültig sind
    	for (final Entry> entry : _data.entrySet()) {
			final @NotNull String validatorName = entry.getKey();
			final @NotNull List list = entry.getValue();
    		final @NotNull HashMap>> mapZeitraeumeBySchulform = new HashMap<>();
    		for (final @NotNull ValidatorFehlerartKontext eintrag : list) {
    			final @NotNull PairNN zeitraum = createZeitraum(eintrag.gueltigVon, eintrag.gueltigBis);
    			addZeitraum(mapZeitraeumeBySchulform, zeitraum, eintrag.hart);
    			addZeitraum(mapZeitraeumeBySchulform, zeitraum, eintrag.muss);
    			addZeitraum(mapZeitraeumeBySchulform, zeitraum, eintrag.hinweis);
    		}
        	for (final Entry>> zeitraeume : mapZeitraeumeBySchulform.entrySet()) {
        		final @NotNull List l = new ArrayList<>();
        		final Schulform sf = Schulform.valueOf(zeitraeume.getKey());
        		if (sf != null)
        			l.addAll(sf.historie());
        		if (!pruefeAufZeitraumueberdeckung(validatorName, createSchulformZeitraumListe(l), zeitraeume.getValue()))
        			throw new CoreTypeException("Fehler beim prüfen der Schulform. Der Validator %s hat ungültige Schulform-Zeitraum-Kombinationen.".formatted(validatorName));
        	}
    	}
	}


	/**
	 * Gibt den Manager für die Schulform und Umgebung zurück, wobei er erzeugt wird, wenn
	 * er nicht existiert.
	 *
	 * @param schulform  die Schulform, für die der Manager benötigt wird
	 * @param isZebras   die entsprechende Umgebung
	 *
	 * @return der Validator-Manager
	 */
	public static @NotNull ValidatorManager getManager(@NotNull final Schulform schulform, final boolean isZebras) {
		if (isZebras) {
			ValidatorManager vm = _managerZebras.get(schulform);
			if (vm == null) {
				vm = new ValidatorManager(schulform, true);
				_managerZebras.put(schulform, vm);
			}
			return vm;
		}

		ValidatorManager vm = _managerSVWS.get(schulform);
		if (vm == null) {
			vm = new ValidatorManager(schulform, false);
			_managerSVWS.put(schulform, vm);
		}
		return vm;
	}


	/**
	 * Gibt die Version der Fehler-Kontext-Daten zurück.
	 *
	 * @return die Version
	 */
	public static long getVersion() {
		return _version;
	}


	/**
	 * Gibt die Liste der Validatorennamen als nicht-leeres Set zurück.
	 *
	 * @return das nicht-leeres Set der Validatoren-Namen
	 */
	public static @NotNull Set getValidatornamenAsSet() {
		return _data.keySet();
	}


	/**
	 * Gibt die Historie der Fehlerart-Kontexte für den angegebenen Validator zurück.
	 *
	 * @param validator   der kanonische Name des Validators
	 *
	 * @return die Historie
	 */
	public static @NotNull List getValidatorHistorie(final @NotNull String validator) {
		final List tmp = _data.get(validator);
		if (tmp == null)
			throw new CoreTypeException("Der Validator " + validator + " existiert nicht in 'validatoren.json'.");
		return tmp;
	}


	/**
	 * Liefert für das angegebene Schuljahr die Map von dem Validatornamen zu der Fehlerart.
	 * Ist der Cache für das Schuljahr noch nicht aufgebaut, so wird dieser erstellt.
	 *
	 * @param schuljahr   das zu prüfende Schuljahr
	 *
	 * @return die Map, die für das gegebene Schuljahr die Fehlerart pro Validator enthält
	 */
	private @NotNull HashMap getValidatornameToFehlerartCache(final int schuljahr) {
		@NotNull
		final HashMap mapValidatorToFehlerart = computeIfAbsentValidatornameToFehlerart(schuljahr);
		// Prüfe, ob die Einträge im Cache sind. Wenn nicht, dann erzeuge die Daten im Cache
		if (mapValidatorToFehlerart.isEmpty())
			createCache(schuljahr);
		return mapValidatorToFehlerart;
	}


	/**
	 * Liefert für das angegebene Schuljahr die Map die einer Fehlerart die Liste der Namen der zugehörigen Validatoren liefert.
	 * Ist der Cache für das Schuljahr noch nicht aufgebaut, so wird dieser erstellt.
	 *
	 * @param schuljahr   das zu prüfende Schuljahr
	 *
	 * @return die Map, die für das gegebene Schuljahr pro Fehlerart die Liste der Validatornamen enthält
	 */
	private @NotNull HashMap> getFehlerartToValidatornameCache(final int schuljahr) {
		@NotNull
		final HashMap> mapFehlerartToValidatorname = computeIfAbsentFehlerartToValidatorname(schuljahr);
		// Prüfe, ob die Einträge im Cache sind. Wenn nicht, dann erzeuge die Daten im Cache
		if (mapFehlerartToValidatorname.isEmpty())
			createCache(schuljahr);
		return mapFehlerartToValidatorname;
	}


	/**
	 * holt das Objekt aus der HashMap oder erzeugt es wenn es nicht vorhanden ist.

	 * @param schuljahr - das Schuljahr, für das das Objekt geholt wird
	 * @return das benötigte Objekt
	 */
	private @NotNull HashMap computeIfAbsentValidatornameToFehlerart(final int schuljahr) {
		HashMap mapValidatorToFehlerart = _mapSchuljahrValidatornameToFehlerart.get(schuljahr);
		if (mapValidatorToFehlerart == null) {
			mapValidatorToFehlerart = new HashMap<>();
			_mapSchuljahrValidatornameToFehlerart.put(schuljahr, mapValidatorToFehlerart);
		}
		return mapValidatorToFehlerart;
	}


	/**
	 * holt das Objekt aus der HashMap oder erzeugt es wenn es nicht vorhanden ist.

	 * @param schuljahr - das Schuljahr, für das das Objekt geholt wird
	 * @return das benötigte Objekt
	 */
	private @NotNull HashMap> computeIfAbsentFehlerartToValidatorname(final int schuljahr) {
		HashMap> mapFehlerartToValidatorname = _mapSchuljahrFehlerartToValidatorname.get(schuljahr);
		if (mapFehlerartToValidatorname == null) {
			mapFehlerartToValidatorname = new HashMap<>();
			_mapSchuljahrFehlerartToValidatorname.put(schuljahr, mapFehlerartToValidatorname);
		}
		return mapFehlerartToValidatorname;
	}


	/**
	 * holt das Objekt aus der HashMap oder erzeugt es wenn es nicht vorhanden ist.

	 * @param art - die Fehlerart, für die die Liste ggfs. erzeugt wird
	 * @param map - die HashMap mit den ArrayLists
	 * @return das benötigte Objekt
	 */
	private static @NotNull List computeIfAbsentFehlerartValidator(final @NotNull ValidatorFehlerart art, final @NotNull Map> map) {
		List list = map.get(art);
		if (list == null) {
			list = new ArrayList<>();
			map.put(art, list);
		}
		return list;
	}


	/**
	 * holt das Objekt aus der HashMap oder erzeugt es wenn es nicht vorhanden ist.

	 * @param schulform - die Fehlerart, für die die Liste ggfs. erzeugt wird
	 * @param map - die HashMap mit den ArrayLists
	 * @return das benötigte Objekt
	 */
	private static @NotNull List> computeIfAbsentZeitraeumeSchulform(final @NotNull String schulform, final @NotNull HashMap>> map) {
		List> list = map.get(schulform);
		if (list == null) {
			list = new ArrayList<>();
			map.put(schulform, list);
		}
		return list;
	}


	/**
	 * Erstellt den Cache für das angegeben Schuljahr.
	 *
	 * @param schuljahr   das Schuljahr
	 */
	private void createCache(final int schuljahr) {
		final @NotNull Map mapValidatorToFehlerart = computeIfAbsentValidatornameToFehlerart(schuljahr);
		mapValidatorToFehlerart.clear();
		final @NotNull Map> mapFehlerartToValidator = computeIfAbsentFehlerartToValidatorname(schuljahr);
		mapFehlerartToValidator.clear();
		final @NotNull List hart = computeIfAbsentFehlerartValidator(ValidatorFehlerart.HART, mapFehlerartToValidator);
		hart.clear();
		final @NotNull List muss = computeIfAbsentFehlerartValidator(ValidatorFehlerart.MUSS, mapFehlerartToValidator);
		muss.clear();
		final @NotNull List hinweis = computeIfAbsentFehlerartValidator(ValidatorFehlerart.HINWEIS, mapFehlerartToValidator);
		hinweis.clear();
		final @NotNull List ungenutzt = computeIfAbsentFehlerartValidator(ValidatorFehlerart.UNGENUTZT, mapFehlerartToValidator);
		ungenutzt.clear();
		for (final Entry> entry : _data.entrySet()) {
			final @NotNull String validatorName = entry.getKey();
			final @NotNull List list = entry.getValue();
			for (final @NotNull ValidatorFehlerartKontext eintrag : list) {
				// Überprüfe die Fehlerart ...
				final boolean hasHart = eintrag.hart.contains(_schulform.name());
				final boolean hasMuss = eintrag.muss.contains(_schulform.name());
				final boolean hasHinweis = eintrag.hinweis.contains(_schulform.name());
				if ((hasHart && hasMuss) || (hasMuss && hasHinweis) || (hasHart && hasHinweis))
					throw new CoreTypeException("Ein Validator kann bei einer Schulform nicht gleichzeitig bei mehreren Fehlerarten aktiv sein.");
				// ... überprüfe Umgebung und Schuljahr ...
				final boolean validatorAktivInUmgebungUndSchuljahr = (_isZebras ? eintrag.zebras : eintrag.svws)
						&& ((eintrag.gueltigVon == null) || (eintrag.gueltigVon <= schuljahr)) && ((eintrag.gueltigBis == null) || (schuljahr <= eintrag.gueltigBis));
				// ... und befülle den Cache
				if (validatorAktivInUmgebungUndSchuljahr && hasHart) {
					mapValidatorToFehlerart.put(validatorName, ValidatorFehlerart.HART);
					hart.add(validatorName);
				} else if (validatorAktivInUmgebungUndSchuljahr && hasMuss) {
					mapValidatorToFehlerart.put(validatorName, ValidatorFehlerart.MUSS);
					muss.add(validatorName);
				} else if (validatorAktivInUmgebungUndSchuljahr && hasHinweis) {
					mapValidatorToFehlerart.put(validatorName, ValidatorFehlerart.HINWEIS);
					hinweis.add(validatorName);
				} else {
					mapValidatorToFehlerart.put(validatorName, ValidatorFehlerart.UNGENUTZT);
					ungenutzt.add(validatorName);
				}
			}
		}
	}


	/**
	 * Gibt die Fehlerart eines Validators für das angegebene Schuljahr zurück.
	 *
	 * @param schuljahr   das Schuljahr
	 * @param validator   der kanonische Name des Validators
	 *
	 * @return die Fehlerart des Validators für das angegebene Schuljahr
	 */
	public ValidatorFehlerart getFehlerartBySchuljahr(final int schuljahr, final @NotNull String validator) {
		return getValidatornameToFehlerartCache(schuljahr).get(validator);
	}


	/**
	 * Setzt die Fehlerart eines Validators für das angegebene Schuljahr.
	 *
	 * @param schuljahr   das Schuljahr
	 * @param validator   der kanonische Name des Validators
	 * @param fehlerart   die Fehlerart des Validators
	 */
	public void setFehlerartBySchuljahr(final int schuljahr, final @NotNull String validator, final @NotNull ValidatorFehlerart fehlerart) {
		//ändere Cache FehlerartToValidatorname
		final @NotNull HashMap> mapFehlerartToValidator = getFehlerartToValidatornameCache(schuljahr);
		//entferne validator aus alter Fehlerartliste, wenn vorhanden
		final ValidatorFehlerart art = getFehlerartBySchuljahr(schuljahr, validator);
		if (art != null) {
			final List list = mapFehlerartToValidator.get(art);
			if (list != null)
				list.remove(validator);
		}
		//füge den Validator in entsprechende Fehlerartliste ein
		computeIfAbsentFehlerartValidator(fehlerart, mapFehlerartToValidator).add(validator);
		//ändere Cache ValidatornameToFehlerart
		final @NotNull HashMap map = getValidatornameToFehlerartCache(schuljahr);
		map.remove(validator);
		map.put(validator, fehlerart);
	}


	/**
	 * Prüft, ob der übergebene Validator in dem angegebenen Schuljar aktiv ist oder nicht.
	 *
	 * @param schuljahr   das Schuljahr
	 * @param validator   der kanonische Name des Validators
	 *
	 * @return true, falls der Validator in dem Schuljahr aktiv ist.
	 */
	public boolean isValidatorActiveInSchuljahr(final int schuljahr, final @NotNull String validator) {
		return (getValidatornameToFehlerartCache(schuljahr).get(validator) != ValidatorFehlerart.UNGENUTZT);
	}


	/**
	 * Trägt aus der Liste von Schulformen den angegebenen Zeitraum in die Liste Zeiträume der jeweiligen Schulform ein.
	 *
	 * @param mapZeitraeumeBySchulform		Die map, die für jede Schulform die Liste der gültigen Zeiträume speichert
	 * @param zeitraum						Ein Zeitraum, in dem die Schulformen in der Liste schulformen gültig sind
	 * @param schulformen					Die Liste der in dem Zeitraum gültigen Schulformen.
	 */
	private static void addZeitraum(final @NotNull HashMap>> mapZeitraeumeBySchulform,
		                            final @NotNull PairNN zeitraum, final @NotNull List schulformen) {
		for (final @NotNull String schulform : schulformen) {
			final @NotNull List> zeitraeumeBySchulform = computeIfAbsentZeitraeumeSchulform(schulform, mapZeitraeumeBySchulform);
			zeitraeumeBySchulform.add(zeitraum);
		}
	}


	/**
	 * Bildet aus der Historie der Schulformen eine Liste der Zeiträume.
	 *
	 * @param historie      die Historie der Schulformen
	 *
	 * @return die Liste der Zeiträume
	 */
	private static @NotNull List> createSchulformZeitraumListe(final @NotNull List historie) {
		final @NotNull List> zeitraeume = new ArrayList<>();
		for (final @NotNull CoreTypeData eintrag : historie)
			zeitraeume.add(createZeitraum(eintrag.gueltigVon, eintrag.gueltigBis));
		return zeitraeume;
	}


	/**
	 * Prüft ob die Zeiträumen der zweiten Liste komplett innerhalb der Zeiträume der ersten Liste liegen. In diesem Zusammenhang wird geprüft,
	 * ob alle Zeiträume, wo ein Validator gültig sein soll auch durch die Gültigkeit bei der entsprechenden Schulform abgedeckt ist. 
*
* Kurzbeschreibung des Algorithmus:
* * Beide Zeitstrahlen können als ggfs. unterbrochene Linien aufgefasst werden. Dort wo der Zeitstrahl 'obermenge' unterbrochen ist, muss * der Zeitstrahl 'untermenge' auch unterbrochen sein. Falls nicht wird false zurückgegeben.
*
* Gültige Beispiele für 'obermenge' enthält 'untermenge':
* Zeitstrahl obermenge: a) ------- b) -------- ---------- c) -------- ----------
* Zeitstrahl untermenge: ------- ----- ----- ----------
* scanPoints: ^ ^ ^^ ^ . . . ^ ^ ^^ ^ ^ // . werden nicht mehr geprüft, da Ergebnis fest steht
*
* Ungültige Beispiele für 'obermenge' enthält 'untermenge'
* Zeitstrahl obermenge: a) ----- b) -------- ---------- c) -------- ----------
* Zeitstrahl untermenge: ------- -------- ----- ----------
* scanPoints: ^ . . ^^ ^^ . . ^^ ^ ^ ^. .. // . werden nicht mehr geprüft, da Ergebnis fest steht
*
* Der Position des Scanpoints wird für das Verfahren nicht benötigt (Es ist immer der kleinere der beiden Punkte die mit iObermenge und iUntermenge * referenziert werden.)
* * @param validatorName der Name des Validators * @param obermenge die Liste der Zeiträume, die die Zeiträume der Untermenge beinhaltet * @param untermenge die Liste der Zeiträume, die überprüft wird, ob sie in der Liste der Obermenge beinhaltet ist. * * @return true, falls untermenge wirklich eine Untermenge von Obermenge ist und ansonsten false */ private static boolean pruefeAufZeitraumueberdeckung(final @NotNull String validatorName, final @NotNull List> obermenge, final @NotNull List> untermenge) { // leere Listen beinhalten nichts -> return false, falls untermenge nicht auch leer ist, sonst true if (obermenge.isEmpty()) return untermenge.isEmpty(); final List listObermenge = getZeitraumListe(validatorName, obermenge); final List listUntermenge = getZeitraumListe(validatorName, untermenge); int iObermenge = 0; int iUntermenge = 0; do { if (iUntermenge >= listUntermenge.size()) return true; if (iObermenge >= listObermenge.size()) return false; // zum nächsten Scanpoint wechseln if (listObermenge.get(iObermenge).intValue() == listUntermenge.get(iUntermenge).intValue()) { iObermenge++; iUntermenge++; } else { if (listObermenge.get(iObermenge) < listUntermenge.get(iUntermenge)) iObermenge++; else iUntermenge++; } // Test am Scanpoint, Abbruch sobald feststeht, dass untermenge keine Untermenge ist. // Ergibt die modulo-2 Berechnung des Index an dieser Stelle am Sccanpoint das Ergebnis 0 // liegt eine Lücke vor und der Index zeigt auf das Ende der Lücke. // Ergibt die modulo-2 Berechnung des Index an dieser Stelle am Sccanpoint das Ergebnis 1 // liegt keine Lücke vor und der Index zeigt auf den Anfang der folgenden Lücke. // Das While wird verlassen sobald obermenge%2=0 und untermenge%2=1 ist und false als Return-Wert feststeht. } while ((iObermenge % 2 == 1) || (iUntermenge % 2 == 0)); return false; } /** * Erstellt ein Liste mit den Jahreszahlen, welche immer eine gerade Anzahl von Einträgen hat. Ein Paar besteht aus Zeitraum-Werten von * und bis. Das nächste Paar wird nur eingetragen, wenn eine Lücke vorhanden ist, so dass ggf. Zeiträume zusammengefasst werden. * Die Zeiträume sind werden hier in der Form [von,bis[ erwartet: Schuljahr 'von' ist Teil des Zeitraums und Schuljahr 'bis' nicht, * so dass kontinuierliche Intervalle entstehen.
*
* Beispiel: 2021-2022, 2023-null => 2021-(2022+1), 2023-null (in aufrufenden Klassen so umgesetzt) * * @param validatorName der Name des Validators * @param vbs die Liste mit den Zeitraum-Paaren * * @return Liste mit den Jahreszahlen, welche die Paare von gültigen Zeiträumen für den Algorithmus aufbereitet enthält. */ private static @NotNull List getZeitraumListe(final @NotNull String validatorName, final @NotNull List> vbs) { final List list = new ArrayList<>(); int i = 0; list.add(vbs.get(0).a); // Eintragen des Beginns while (i + 1 < vbs.size()) { // Prüfe auf überlappende Zeiträume, die nicht erlaubt sind. if (vbs.get(i).b > vbs.get(i + 1).a) throw new CoreTypeException("Fehler beim prüfen der Zeiträume bei dem Validator '%s'. Die Zeiträume von %s sind überlappend definiert.".formatted(validatorName, vbs.get(0).getClass().getSimpleName())); if (vbs.get(i).b < vbs.get(i + 1).a) { // Lücke gefunden list.add(vbs.get(i).b); list.add(vbs.get(i + 1).a); } i++; } list.add(vbs.get(i).b); return list; } /** * Erzeugt ein PairNN, dass den Anfangszeitpunkt und den Endzeitpunkt enthält. Die Zeiträume sind werden * hier in der Form [von,bis[ erwartet, Schuljahr 'von' ist Teil des Zeitraums Schuljahr 'bis' nicht, * Die null-Werte aus gueltigVon und gueltigBis werden in 0 bzw. MAX_VALUE übersetzt, sowie der * gueltigBis-Wert um 1 erhöht, damit kontinuierliche Zeiträume entstehen können. * * @param von Beginn des Zeitraums * @param bis Ende des Zeitraums * * @return Das Zeitraum-Paar mit übersetzten Null-Werten. */ private static @NotNull PairNN createZeitraum(final Integer von, final Integer bis) { /* Die null-Werte aus gueltigVon und gueltigBis werden in MIN_VALUE bzw. MAX_VALUE übersetzt, sowie der gueltigBis-Wert * um 1 erhöht, damit kontinuierliche Zeiträume entstehen können. */ final @NotNull Integer v = (von == null ? Integer.MIN_VALUE : von); final @NotNull Integer b = (bis == null ? Integer.MAX_VALUE : bis + 1); return new PairNN<>(v, b); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy