gdv.xport.satz.Teildatensatz Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gdv-xport-lib Show documentation
Show all versions of gdv-xport-lib Show documentation
gdv-xport-lib ist die Java-Bibliothek fuer den Umgang mit dem GDV-Format.
Sie erleichtert den Export und Export dieses Datenformats.
/*
* 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.SatzTyp;
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 java.util.Map.Entry;
/**
* 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 Satz {
private static final Logger LOG = LogManager.getLogger(Teildatensatz.class);
/** Diese Map dient fuer den Zugriff ueber den Namen. */
private final Map datenfelder = new HashMap<>();
/** Dieses Set dient zum Zugriff ueber die Nummer. */
private final SortedSet sortedFelder = new TreeSet<>();
/** Dieses Feld brauchen wir, um die Satznummer abzuspeichern. */
protected 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();
}
/**
* 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) {
super(satzTyp, 0);
initSatznummer(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 Satz satz, final int nr) {
super(satz, 0);
initSatznummer(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, 0);
this.satznummer = other.satznummer;
for (Entry entry : other.datenfelder.entrySet()) {
Feld copy = (Feld) entry.getValue().clone();
this.datenfelder.put(entry.getKey(), copy);
this.sortedFelder.add(copy);
}
remove(Bezeichner.SATZART);
initDatenfelder();
}
/**
* Inits the satznummer.
*
* @param nr the nr
*/
private void initSatznummer(final 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));
this.initDatenfelder();
}
/* (non-Javadoc)
* @see gdv.xport.satz.Satz#createTeildatensaetze(int)
*/
@Override
protected void createTeildatensaetze(final int n) {
assert n == 0 : "ein Teildatensatz hat keine weiteren Teildatensaetze";
}
private void initDatenfelder() {
this.add(this.getSatzartFeld());
}
/**
* Liefert die Satznummer zurueck. Sie wurde aus Symmetriegruenden
* zu {@link #setSatznummer(Zeichen)} eingefuehrt und loest die alte
* getNummer()-Methode ab.
*
* @return Satznummer als einzelnes Zeichen ('1' ... '9')
* @since 5.0
*/
public Zeichen 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 new Zeichen(this.satznummer);
}
/**
* Da nicht alle Satzarten die Satznummer am Ende des Satzes haben, kann
* man dies ueber diese Methode korrigieren.
*
* TODO: wird ab v7 nicht mehr unterstuetzt
*
*
* @param satznummer das neue Feld fuer die Satznummer
* @since 3.2
* @deprecated ab 5.1 nicht mehr noetig, da {@link #getSatznummer()}
* jetzt die tatsaechliche Satznummer liefert
*/
@Deprecated
public void setSatznummer(Zeichen satznummer) {
String nr = this.satznummer.getInhalt();
remove(Bezeichner.SATZNUMMER);
this.satznummer = new Satznummer(satznummer);
this.satznummer.setInhalt(nr);
add(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 : datenfelder.values()) {
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)) {
if (isSatznummer(f)) {
remove(f);
LOG.debug(f + " is removed from " + this);
break;
} else {
throw new IllegalArgumentException("conflict: " + feld + " overlaps with " + f);
}
}
}
setUpFeld(feld);
datenfelder.put(feld.getBezeichner(), feld);
if (!sortedFelder.add(feld)) {
LOG.debug("Bezeichner {} schon vorhanden in {} {}.", feld.getBezeichner(), this, this.getSatznummer());
}
}
private void setUpFeld(Feld feld) {
if (feld.getBezeichnung().startsWith("Satznummernwiederholung")) {
feld.setInhalt(this.satznummer.getInhalt());
} else if (feld.getBezeichnung().startsWith("Satznummer")) {
LOG.debug("{}({}) einfuegen in {} +", feld.getBezeichnung(), feld.getBezeichner().getTechnischerName(), this);
feld.setInhalt(this.satznummer.getInhalt());
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());
}
}
/**
* Checks if is satznummer.
*
* @param feld the feld
* @return true, if is satznummer
*/
private static boolean isSatznummer(final Feld feld) {
if ((feld.getByteAdresse() == 256) && (feld.getAnzahlBytes() == 1)) {
String bezeichnung = feld.getBezeichnung();
return bezeichnung.length() <= 11 && bezeichnung.startsWith("Satznummer");
}
return false;
}
/**
* 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) {
this.remove(feld.getBezeichnung());
}
/**
* 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) {
Feld feld = this.datenfelder.get(bezeichner);
if (feld != null) {
this.datenfelder.remove(bezeichner);
this.sortedFelder.remove(feld);
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) {
Feld x = this.getFeld(name);
if (x == Feld.NULL_FELD) {
throw new IllegalArgumentException("Feld \"" + name + "\" not found");
}
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
*/
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 {
x.setInhalt(value);
} catch (IllegalArgumentException iae) {
throw new IllegalArgumentException(String.format(
"%s: illegal value '%s' for %s", this.toShortString(), value, x), iae);
}
}
/**
* Liefert das gewuenschte Feld.
*
* @param bezeichner gewuenschter Bezeichner des Feldes
* @return das gesuchte Feld
*/
@Override
public Feld getFeld(final Bezeichner bezeichner) {
for (Bezeichner b : bezeichner.getVariants()) {
Feld feld = datenfelder.get(b);
if (feld != null) {
return feld;
}
}
return findFeld(bezeichner);
}
private Feld findFeld(final Bezeichner bezeichner) {
for (Entry entry : datenfelder.entrySet()) {
if (entry.getKey().getName().equals(bezeichner.getName())) {
return entry.getValue();
}
}
throw new IllegalArgumentException("Feld \"" + bezeichner + "\" nicht in " + this.toShortString()
+ " nicht vorhanden!");
}
/**
* Liefert das Feld mit der gewuenschten Nummer zurueck.
*
* @param nr z.B. 1
* @return das Feld (z.B. mit der Satzart)
*/
public Feld getFeld(final int nr) {
int myNr = nr;
// 2018er-Version: in SA0100, TD1: es gibt kein Feld-Nr 27! Die SatzNr ist
// Feld 26 !!!
// 2018er-Version: in SA0210.050, TD1: es gibt kein Feld-Nr 35! Die SatzNr
// ist Feld 34 !!!
// 2018er-Version: in SA0220.010.13.1, TD1: es gibt kein Feld-Nr 46! Die
// Satznummer ist Feld 45 !!!
// 2018er-Version: in SA0600, TD2: es gibt kein Feld-Nr 13! Die Satznummer
// ist Feld 12 !!!
// 2018er-Version: in SA0600, TD3: es gibt kein Feld-Nr 14! Die Satznummer
// ist Feld 13 !!!
// 2018er-Version: in SA9950, TD1: es gibt kein Feld-Nr 11! Die Satznummer
// ist Feld 10 !!!
// 2018er-Version: in SA9951, TD1: es gibt kein Feld-Nr 11! Die Satznummer
// ist Feld 10 !!!
switch (this.getGdvSatzartName()) {
case "0100":
if (("1").equals(this.getSatznummer()
.getInhalt()) && myNr == 27)
myNr--;
break;
case "0210.050":
if (("1").equals(this.getSatznummer()
.getInhalt()) && myNr == 35)
myNr--;
break;
case "0220.010.13.1":
if (("1").equals(this.getSatznummer()
.getInhalt()) && myNr == 46)
myNr--;
break;
case "0600":
if (("2").equals(this.getSatznummer()
.getInhalt()) && myNr == 13)
{
myNr--;
}
else if (("3").equals(this.getSatznummer()
.getInhalt()) && myNr == 14)
myNr--;
break;
case "9950":
case "9951":
if (("1").equals(this.getSatznummer()
.getInhalt()) && myNr == 11)
myNr--;
break;
default:
break;
}
return (Feld) sortedFelder.toArray()[myNr - 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 : getFelder()) {
if (adresse.intValue() == f.getByteAdresse()) {
return f;
}
}
throw new IllegalArgumentException("invalid address " + adresse);
}
/**
* 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()) {
if (this.datenfelder.containsKey(b)) {
return true;
}
for (Entry entry : datenfelder.entrySet()) {
if (entry.getKey().getName().equals(bezeichner.getName())) {
return true;
}
}
}
return false;
}
/**
* Ueberprueft, ob das uebergebene Feld vorhanden ist.
*
* Anmerkung: Es wird nur der Name ueberprueft. D.h. es wird nicht
* ueberprueft, ob es evtl. einen Konflikt mit der Start- und End-Adresse
* gibt.
*
*
* @param feld the feld
* @return true, if successful
* @since 1.0
*/
public boolean hasFeld(final Feld feld) {
return this.datenfelder.containsKey(feld.getBezeichner());
}
/**
* Liefert alle Felder in der Reihenfolge innerhalb des Teildatensatzes
* zurueck.
*
* @return List der Felder (sortiert)
* @since 0.2
*/
@Override
public Collection getFelder() {
return sortedFelder;
}
/**
* 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 = Config.hasEOD() ? Config.getEOD() : "";
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);
for (int i = 0; i < 256; i++) {
data.append(' ');
}
for (Entry entry : datenfelder.entrySet()) {
Feld feld = datenfelder.get(entry.getKey());
int start = (feld.getByteAdresse() - 1) % 256;
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.values()) {
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.values()) {
if (!feld.isValid()) {
LOG.info(feld + " is not valid");
return false;
}
}
return true;
}
@Override
public List validate(Config validationConfig) {
List violations = new ArrayList<>();
for (Feld feld : datenfelder.values()) {
violations.addAll(feld.validate(validationConfig));
}
return violations;
}
@Override
public String toShortString() {
if (sortedFelder.size() < 4)
return String.format("Teildatensatz %c Satzart %04d", this.getSatznummer().toChar(),
this.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);
}
}