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

gdv.xport.satz.Teildatensatz Maven / Gradle / Ivy

Go to download

gdv-xport-lib ist die Java-Bibliothek fuer den Umgang mit dem GDV-Format. Sie erleichtert den Export und Export dieses Datenformats.

The newest version!
/*
 * Copyright (c) 2009 - 2021 by Oli B.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * (c)reated 04.10.2009 by Oli B. ([email protected])
 */

package gdv.xport.satz;

import gdv.xport.config.Config;
import gdv.xport.feld.*;
import gdv.xport.io.ImportException;
import gdv.xport.util.NotUniqueException;
import gdv.xport.util.SatzTyp;
import gdv.xport.util.SimpleConstraintViolation;
import net.sf.oval.ConstraintViolation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.io.Writer;
import java.util.*;

import static gdv.xport.feld.Bezeichner.SATZART;
import static gdv.xport.feld.Bezeichner.SPARTE;

/**
 * Ein Teildatensatz hat immer genau 256 Bytes. Dies wird beim Export
 * beruecksichtigt. Und ein Teildatensatz besteht aus mehreren Datenfeldern.
 *
 * @author [email protected]
 * @since 04.10.2009
 */
public class Teildatensatz extends Datensatz {

    private static final Logger LOG = LogManager.getLogger(Teildatensatz.class);
    private final Collection datenfelder = Config.getInstance().isDebug() ? new TreeSet<>() : new ArrayList<>();
    /** Dieses Feld brauchen wir, um die Satznummer abzuspeichern. */
    private Satznummer satznummer = new Satznummer();

    /**
     * Instantiiert einen neuen Teildatensatz mit der angegebenen Satzart.
     *
     * @param satzTyp z.B. 0220.050
     */
    public Teildatensatz(final SatzTyp satzTyp) {
        super(satzTyp, 0);
        this.initDatenfelder(satzTyp);
    }

    /**
     * Instantiiert einen neuen Teildatensatz mit der angegebenen Satzart und
     * Nummer.
     *
     * @param satzTyp z.B. 0220.050
     * @param nr      Nummer des Teildatensatzes (zwischen 1 und 9)
     */
    public Teildatensatz(final SatzTyp satzTyp, final int nr) {
        this(satzTyp);
        setSatznummer(nr);
        this.setGdvSatzartName(satzTyp.toString());
// 		if (satzTyp.hasGdvSatzartNummer())
//			this.setGdvSatzartNummer(String.valueOf(satzTyp.getGdvSatzartNummer()));
    }

    /**
     * Instantiiert einen neuen Teildatensatz mit der angegebenen Satzart, Nummer
     * und Version des zugeheorigen Satzes.
     *
     * @param satz        z.B. 100
     * @param nr          Nummer des Teildatensatzes (zwischen 1 und 9)
     */
    public Teildatensatz(final Datensatz satz, final int nr) {
        super(satz);
        initDatenfelder(satz.getSatzTyp());
        setSatznummer(nr);
    }

    /**
     * Dies ist der Copy-Constructor, falls man eine Kopie eines Teildatensatzes
     * braucht.
     *
     * @param other der andere Teildatensatz
     */
    public Teildatensatz(final Teildatensatz other) {
        super(other);
        this.satznummer = other.satznummer;
        for (Feld f : other.datenfelder) {
            Feld copy = (Feld) f.clone();
            this.datenfelder.add(copy);
        }
    }

    private void setSatznummer(int nr) {
        if ((nr < 1) || (nr > 9)) {
            throw new IllegalArgumentException("Satznummer (" + nr
                    + ") muss zwischen 1 und 9 liegen");
        }
        this.satznummer.setInhalt(Character.forDigit(nr, 10));
    }

    private void initDatenfelder(SatzTyp satzTyp) {
        NumFeld satzart = new NumFeld((SATZART), 4, ByteAdresse.of(1)).mitConfig(getConfig());
        satzart.setInhalt(satzTyp.getSatzart());
        this.add(satzart);
    }

    @Override
    public NumFeld getSatzartFeld() {
        Optional satzart = findFeld(Bezeichner.SATZART);
        return satzart.map(feld -> (NumFeld) feld).orElseGet(() -> new NumFeld(SATZART, 4, ByteAdresse.of(1)));
    }

    /**
     * Liefert die Satznummer zurueck. Sie loest die alte
     * getNummer()-Methode ab.
     *
     * @return Satznummer als einzelnes Zeichen ('1' ... '9')
     * @since 5.0
     */
    public Satznummer getSatznummer() {
        if ((this.satznummer.getByteAdresse() == 256) && hasFeld(Bezeichner.SATZNUMMER)) {
            Satznummer nr = getFeld(Bezeichner.SATZNUMMER, Satznummer.class);
            if (nr.isEmpty() || nr.isInvalid()) {
                nr.setInhalt(this.satznummer.getInhalt());
            }
            this.satznummer = nr;
        }
        return this.satznummer;
    }

    /**
     * Fuegt das angegebene Feld in den Teildatensatz ein.
     * Bei Einfuegen wird ueberprueft, ob es zu Ueberschneidungen mit
     * anderen Feldern kommt. Ausnahme hierbei ist das Satznummern-Feld
     * auf Byte 256, mit dem der Teildatensatz vorinitialisiert wurde.
     * Kommt es hier zu einer Ueberlappung, wird das Satznummern-Feld
     * entfernt, da nicht alle Saetze dieses Feld besitzen.
     *
     * @param feld Feld mit Name
     */
    @Override
    public void add(final Feld feld) {
        for (Feld f : getFelder()) {
            if (LOG.isDebugEnabled() && f.getBezeichnung().startsWith("Satznummer")
                    && feld.getBezeichnung().startsWith("Satznummer")) {
                LOG.debug(f.getBezeichnung() + "(" + f.getBezeichner().getTechnischerName() + ") gefunden in "
                        + this + this.getSatznummer());
            }
            if (!feld.equals(f) && feld.overlapsWith(f)) {
                throw new IllegalArgumentException("conflict: " + feld + " overlaps with " + f);
            } else if (feld.compareTo(f) == 0) {
                remove(f);
                LOG.debug("{} wird durch {} ersetzt.", f, feld);
            }
        }
        setUpFeld(feld);
        this.datenfelder.add(feld);
    }

    private void setUpFeld(Feld feld) {
        if (feld.getBezeichnung().startsWith("Satznummernwiederholung")) {
            feld.setInhalt(getSatznummer().getInhalt());
        } else if (feld.getBezeichnung().startsWith("Satznummer")) {
            LOG.debug("{}({}) einfuegen in {} +", feld.getBezeichnung(), feld.getBezeichner().getTechnischerName(), this);
            feld.setInhalt(getSatznummer().getInhalt());
            if (this.getSatznummer().getByteAdresse() >= feld.getByteAdresse()) {
                this.satznummer = new Satznummer(feld);
            }
        } else if (feld.getBezeichner().equals(Bezeichner.ZUSAETZLICHE_SATZKENNUNG)) {
            feld.setInhalt("X");
        } else if (feld.getBezeichnung().startsWith("Vorzeichen")) {
            LOG.debug("{}({}) einfuegen in {} +", feld.getBezeichnung(), feld.getBezeichner().getTechnischerName(), this);
            feld.setInhalt("+");
        } else if (this.getSatzart() == 1 && feld.getBezeichner().getTechnischerName().equals("Satzart0001")) {
            LOG.debug("{}({}) einfuegen in {} {}}", feld.getBezeichnung(), feld.getBezeichner().getTechnischerName(),
                    this, this.getSatzversion());
            feld.setInhalt(this.getSatzversion().getInhalt());
        } else if (this.getGdvSatzartName().startsWith("0220.020")
                && feld.getBezeichner().getTechnischerName().startsWith("FolgeNrZurLaufendenPersonenNrUnterNr")) {
            // bei den 0220.020er-Saetzen ist die KrankenFolgeNr wichtig fuer die Erkennbarkeit der
            // Satzart.
            LOG.debug("{}({}) einfuegen in {} +", feld.getBezeichnung(), feld.getBezeichner()
                    .getTechnischerName(), this);
            feld.setInhalt(this.getSatzTyp().getKrankenFolgeNr());
        }
    }

    /**
     * Falls ein Feld zuviel gesetzt wurde, kann es mit 'remove" wieder
     * entfernt werden.
     *
     * @param feld das Feld, das entfernt werden soll
     */
    public void remove(final Feld feld) {
        datenfelder.remove(feld);
    }

    /**
     * Falls ein Feld zuviel gesetzt wurde, kann es mit 'remove" wieder entfernt
     * werden.
     *
     * @param bezeichner der Feld-Beezeichner
     * @since 1.0
     */
    @Override
    public void remove(final Bezeichner bezeichner) {
        if (hasFeld(bezeichner)) {
            datenfelder.remove(getFeld(bezeichner));
            LOG.debug("{} was removed from {}.", bezeichner, this);
        }
    }

    /**
     * Setzt das gewuenschte Feld. Falls es nicht vorhanden ist, wird analog
     * zur Oberklasse eine {@link IllegalArgumentException} geworfen.
     *
     * @param name der Name des Feldes
     * @param value der gewuenschte Werte als String
     * @since 5.2
     */
    @Override
    public void setFeld(final Bezeichner name, final String value) {
        List felder = this.getAllFelder(name);
        if (felder.isEmpty()) {
            throw new IllegalArgumentException("Feld \"" + name + "\" not found");
        }
        if (felder.size() > 1) {
            LOG.info("Mit Bezeichner {} werden mehrere Felder in '{}' mit '{}' belegt: {}", name,this, value, felder);
            throw new NotUniqueException(String.format("Bezeichner '%s' in %s nicht eindeutig: %s, %s...", name, toShortString(), felder.get(0), felder.get(1)));
        }
        for (Feld x : felder) {
            setFeld(x, value);
        }
    }

    /**
     * Setzt das gewuenschte Feld anhand der uebergebenen ByteAdresse.
     *
     * @param adresse Adresse des gewuenschten Feldes
     * @param value   Wert
     * @since 5.0
     * @deprecated wurde durch {@link Teildatensatz#setFeld(ByteAdresse, String)} ersetzt
     */
    @Deprecated
    public void set(final ByteAdresse adresse, final String value) {
        Feld x = this.getFeld(adresse);
        x.setInhalt(value);
    }

    /**
     * Setzt das gewuenschte Feld anhand der uebergebenen ByteAdresse.
     *
     * @param adresse Adresse des gewuenschten Feldes
     * @param value   Wert
     * @since 5.2
     */
    @Override
    public void setFeld(final ByteAdresse adresse, final String value) {
        Feld x = this.getFeld(adresse);
        setFeld(x, value);
    }

    private void setFeld(Feld x, String value) {
        try {
            LOG.debug("{} in '{}' wird mit '{}' belegt.", x, this, value);
            x.setInhalt(value);
        } catch (IllegalArgumentException iae) {
            throw new IllegalArgumentException(String.format(
                    "%s: illegal value '%s' for %s", this.toShortString(), value, x), iae);
        }
    }

    public void setSparte(final int x) {
        NumFeld numFeld = new NumFeld(SPARTE, 3, ByteAdresse.of(11));
        if (!hasFeld(SPARTE)) {
            add(numFeld);
        } else {
            numFeld = getFeld(SPARTE, NumFeld.class);
        }
        numFeld.setInhalt(x);
    }

    @Override
    public int getSparte() {
        return getFeld(SPARTE, NumFeld.class).toInt();
    }

    /**
     * Liefert das gewuenschte Feld.
     * 

* Falls kein Feld mit dem Bezeichner vorhanden ist, wird eine * {@link IllegalArgumentException} geworfen. Ebenso wenn das Feld * nicht eindeutig ist und unterschiedliche Werte hat. Dann gibt es * eine {@link NotUniqueException} (Ausnahme: Satznummer). *

* @param bezeichner gewuenschter Bezeichner des Feldes * @return das gesuchte Feld */ @Override public Feld getFeld(final Bezeichner bezeichner) { List found = getAllFelder(bezeichner); if (found.isEmpty()) { Optional feld = findFeld(bezeichner); if (feld.isPresent()) { return feld.get(); } throw new IllegalArgumentException("Feld \"" + bezeichner + "\" nicht in " + this.toShortString() + " nicht vorhanden!"); } else if (found.size() > 1) { checkUniqueness(found); } return found.get(0); } private static void checkUniqueness(List felder) { Feld f1 = felder.get(0); for (int i = 1; i < felder.size(); i++) { Feld fn = felder.get(i); if (!f1.getInhalt().equals(fn.getInhalt())) { throw new NotUniqueException(String.format("same Bezeichner, different values: '%s', '%s'", f1, fn)); } } LOG.debug("{} hat gleichen Wert wie gleichlautende Felder.", f1); } private List getAllFelder(Bezeichner bezeichner) { List found = new ArrayList<>(); for (Bezeichner b : bezeichner.getVariants()) { for (Feld feld : datenfelder) { if (b.equals(feld.getBezeichner())) { found.add(feld); } } } // String technischerName = bezeichner.getTechnischerName(); // char last = technischerName.charAt(technischerName.length()-1); // if (!Character.isDigit(last)) { // for (int i = 2; i <= 12; i++) { // found.addAll(getAllFelder(Bezeichner.of(technischerName + i))); // } // } return found; } private Optional findFeld(final Bezeichner bezeichner) { if (datenfelder == null) { return Optional.empty(); } for (Feld f : datenfelder) { if (f.getBezeichner().getName().equals(bezeichner.getName())) { return Optional.of(f); } } return Optional.empty(); } /** * Liefert das Feld mit der gewuenschten Nummer zurueck. *

* In der Beschreibung zur 2018er-Version gibt es bei der Feld-Nr. bei * manchen Satzarten Ungereimtheiten. So hat im Teildatensatz 1 der * Satzart 100 das Feld fuer die Satznummer die Feld-Nr. 27 und es gibt * kein Feld 26. Auch bei diesen Satzarten ist eine Luecke in der * Nummerierung: *

*
    *
  • Satart 0100, TDS 1: Feld 26 fehlt
  • *
  • Satart 0210.050, TDS 1: Feld 34 fehlt
  • *
  • Satart SA0220.010.13.1, TDS 1: Feld 45 fehlt
  • *
  • Satart 0600, TDS 2: Feld 12 fehlt
  • *
  • Satart 0600, TDS 3: Feld 13 fehlt
  • *
  • Satart 9950, TDS 1: Feld 10 fehlt
  • *
  • Satart 9951, TDS 1: Feld 10 fehlt
  • *
*

* In der 2023er-Version wurde das in der Beschreibung korrigiert. * Um den Zugriff zu vereinfachen, wird daher bei Feld-Nummern, die * zu gross sind, das letzte Feld zurueckgegeben. *

* * @param nr z.B. 1 * @return das Feld (z.B. mit der Satzart) */ public Feld getFeld(int nr) { Feld[] array = getFelder().toArray(new Feld[0]); if (nr > array.length) { LOG.info("Feld {} in {} wird auf letztes Feld {} abgebildet.", nr, toShortString(), array.length); nr = array.length; } return array[nr - 1]; } /** * Liefert das Feld mit der angegebenen Byte-Adresse. Im Gegensatz zur * Nr. in {@link #getFeld(int)} aendert sich diese nicht, wenn neue * Elemente in einem Teildatensatz hinzukommen. * * @param adresse zwischen 1 und 256 * @return das entsprechende Feld * @since 5.0 */ public Feld getFeld(final ByteAdresse adresse) { for (Feld f : datenfelder) { if (adresse.intValue() == f.getByteAdresse()) { return f; } } throw new IllegalArgumentException( String.format("Adresse %s existiert nicht in %s", adresse, this.toShortString())); } /** * Fraegt ab, ob das entsprechende Feld vorhanden ist. * * @param bezeichner gewuenschter Bezeichner des Feldes * @return true / false * @see gdv.xport.satz.Satz#hasFeld(Bezeichner) * @since 1.0 */ @Override public boolean hasFeld(final Bezeichner bezeichner) { for (Bezeichner b : bezeichner.getVariants()) { for (Feld f : datenfelder) { if (b.equals(f.getBezeichner())) { return true; } } } return false; } /** * Ueberprueft, ob das uebergebene Feld vorhanden ist. *

* Anmerkung: Mit 4.4 wird nicht nur der Name ueberprueft sondern alle * Attribute. *

* * @param feld the feld * @return true, if successful * @since 1.0 */ public boolean hasFeld(final Feld feld) { for (Feld f : datenfelder) { if (feld.equals(f)) { return true; } } return false; } /** * Ueberprueft, ein Feld mit der angegebenen Adresse vorhanden ist. * * @param adresse gesuchte Adresse * @return true, if successful * @since 7.1 */ @Override public boolean hasFeld(final ByteAdresse adresse) { for (Feld f : datenfelder) { if (adresse.intValue() == f.getByteAdresse()) { return true; } } return false; } /** * Liefert alle Felder in der Reihenfolge innerhalb des Teildatensatzes * zurueck. * * @return List der Felder (sortiert) * @since 0.2 */ @Override public final Collection getFelder() { return new TreeSet<>(datenfelder); } /** * Liefert die Liste der speziellen Kennzeichen zur Identifikation beim Import zurueck. * Jedes Element enthaelt Byte-Adresse und Inhalt. * * @return Liste der speziellen Kennzeichen */ public List getSatzIdent() { String[] identBezeichner = {"FolgeNrZurLaufendenPersonenNrUnterNrBzwLaufendenNrTarif", "FolgeNrZurLaufendenPersonenNrUnterNrLaufendeNrTarif", "SatzNr", "SatzNr1", "SatzNr2", "SatzNr3", "SatzNr4", "SatzNr9", "SatzNrnwiederholung", "SatzNrnwiederholung1", "SatzNrnwiederholung2", "SatzNrnwiederholung3", "Satznummer", "ZusaetzlicheSatzkennung"}; List satzIdent = new ArrayList<>(); for (String s : identBezeichner) { Bezeichner b = Bezeichner.of(s); if (hasFeld(b)) { satzIdent.add(getFeld(b, Zeichen.class)); } } return satzIdent; } /* (non-Javadoc) * @see gdv.xport.satz.Datensatz#export(java.io.Writer) */ @Override public void export(final Writer writer) throws IOException { String eod = getConfig().getProperty("gdv.eod", System.lineSeparator()); export(writer, eod); } /* (non-Javadoc) * @see gdv.xport.satz.Satz#export(java.io.Writer, java.lang.String) */ @Override public void export(final Writer writer, final String eod) throws IOException { StringBuilder data = new StringBuilder(256); data.append(" ".repeat(256)); for (Feld feld : datenfelder) { int start = feld.getByteAdresse() - 1; int end = start + feld.getAnzahlBytes(); data.replace(start, end, feld.getInhalt()); } assert data.length() == 256 : "Teildatensatz ist " + data.length() + " und nicht 256 Bytes lang"; writer.write(data.toString()); writer.write(eod); } /* (non-Javadoc) * @see gdv.xport.satz.Satz#importFrom(java.lang.String) */ @Override public Teildatensatz importFrom(final String content) throws IOException { for (Feld feld : datenfelder) { int begin = (feld.getByteAdresse() - 1) % 256; int end = begin + feld.getAnzahlBytes(); if (end > content.length()) { throw new ImportException("input string is too short (" + (end - content.length()) + " bytes missing): " + content); } String s = content.substring(begin, end); feld.setInhalt(s); } return this; } /* (non-Javadoc) * @see gdv.xport.satz.Satz#isValid() */ @Override public boolean isValid() { if (!super.isValid()) { return false; } for (Feld feld : datenfelder) { if (!feld.isValid()) { LOG.info(feld + " is not valid"); return false; } } return true; } @Override public List validate(Config validationConfig) { List violations = validateSatznummern(validationConfig); for (Feld feld : datenfelder) { violations.addAll(feld.validate(validationConfig)); } return violations; } private List validateSatznummern(Config validationConfig) { List violations = new ArrayList<>(); if (validationConfig.getValidateMode() != Config.ValidateMode.OFF) { LOG.debug("Satznummern werden validiert in {}.", this); Zeichen satznr = getSatznummer(); for (Feld feld : datenfelder) { if (feld.getBezeichner().isVariantOf(Bezeichner.SATZNUMMER) && !satznr.getInhalt().equals(feld.getInhalt())) { ConstraintViolation cv = new SimpleConstraintViolation("different Satznummern: " + satznr, this, feld); violations.add(cv); } } } return violations; } @Override public String toShortString() { if (datenfelder.size() < 4) return String.format("Teildatensatz Satzart %04d", getSatzart()); else return String.format("Teildatensatz %c Satzart %s", this.getSatznummer().toChar(), this.getSatzTyp()); } /** * Legt eine Kopie des Teildatensatzes an. * * @return Kopie * @see Cloneable */ @Override public Object clone() { return new Teildatensatz(this); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy