gdv.xport.feld.Feld 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.feld;
import com.fasterxml.jackson.annotation.JsonIgnore;
import de.jfachwert.SimpleValidator;
import de.jfachwert.Text;
import gdv.xport.config.Config;
import gdv.xport.util.SimpleConstraintViolation;
import net.sf.oval.ConstraintViolation;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.validation.ValidationException;
import java.io.IOException;
import java.io.Serializable;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
/**
* Die Feld-Klasse bezieht ihre Information hauptsaechlich aus Enum-Klassen wie
* Feld100 oder Feld1bis7, die mit Annotationen versehen sind.
*
* @author oliver
* @since 04.10.2009
*/
public class Feld implements Comparable, Cloneable, Serializable {
private static final Logger LOG = LogManager.getLogger(Feld.class);
/** statt "null". */
public static final Feld NULL_FELD = new Feld();
private final Bezeichner bezeichner;
protected String inhalt;
private final byte byteAdresse;
private final byte length;
private byte ausrichtung;
protected final Config config;
/**
* Legt ein neues Feld an. Dieser Default-Konstruktor ist fuer Unterklassen
* vorgesehen. Da er aber auch fuer die {@link Cloneable}-Implementierung
* benoetigt wird, ist er 'public'.
*
* @since 1.0
*/
public Feld() {
this(Bezeichner.UNBEKANNT, 213, 43, Align.LEFT);
}
/**
* Instantiates a new feld.
*
* @param name
* the name
* @param s
* the s
* @param alignment
* the alignment
*/
public Feld(final String name, final String s, final Align alignment) {
this(Bezeichner.of(name), 1, s, alignment);
}
/**
* Liefert eine neues Feld mit neuer Konfiguration
*
* @param c neue Konfiguration
* @return neues NumFeld
* @since 5.3
*/
public Feld mitConfig(Config c) {
return new Feld(this, c);
}
/**
* Erzeugt ein neues Feld.
*
* @param name Name
* @param start Start-Adresse
* @param s der Inhalt
* @param alignment the alignment
* @deprecated durch entsprechenden Constructor mit ByteAdresse ersetzt
* (TODO: wird mit v8 entsorgt)
*/
@Deprecated
public Feld(final Bezeichner name, final int start, final String s, final Align alignment) {
this.bezeichner = name;
this.inhalt = s;
this.length = toByteLength(s.length());
this.byteAdresse = ByteAdresse.of(start).byteValue();
this.ausrichtung = alignment.getCode();
this.config = Config.getInstance();
}
/**
* Erzeugt ein neues Feld.
*
* @param name Name
* @param start Start-Adresse
* @param s der Inhalt
* @param alignment Ausrichtung
* @since 7.0 (07-Jan-2024)
*/
public Feld(final Bezeichner name, final ByteAdresse start, final String s, final Align alignment) {
this.bezeichner = name;
this.inhalt = s;
this.length = toByteLength(s.length());
this.byteAdresse = start.byteValue();
this.ausrichtung = alignment.getCode();
this.config = Config.getInstance();
}
private static byte toByteLength(int n) {
if (n > 0) {
return ByteAdresse.of(n).byteValue();
} else {
return ByteAdresse.of(1).byteValue();
}
}
/**
* Erzeugt ein neues Feld.
*
* @param bezeichner der Name des Felds
* @param length die Anzahl der Bytes
* @param start die Start-Adresse
* @param alignment die Ausrichtung
* @since 1.0
* @deprecated durch entsprechenden Constructor mit ByteAdresse ersetzt
* (TODO: wird mit v8 entsorgt)
*/
@Deprecated
public Feld(final Bezeichner bezeichner, final int length, final int start, final Align alignment) {
this(bezeichner, length, start, alignment, Config.getInstance());
}
/**
* Erzeugt ein neues Feld.
*
* @param bezeichner der Name des Felds
* @param length die Anzahl der Bytes
* @param start die Start-Adresse
* @param alignment die Ausrichtung
* @since 7.0 (07-Jan-2024)
*/
public Feld(final Bezeichner bezeichner, final int length, final ByteAdresse start, final Align alignment) {
this(bezeichner, length, start, alignment, Config.getInstance());
}
@Deprecated // TODO: wird mit v8 entsorgt
protected Feld(Bezeichner bezeichner, int length, int start, Align alignment, Config config) {
this.bezeichner = bezeichner;
this.inhalt = StringUtils.repeat(" ", length);
this.length = toByteLength(length);
this.byteAdresse = ByteAdresse.of(start).byteValue();
this.ausrichtung = alignment.getCode();
this.config = config;
}
protected Feld(Bezeichner bezeichner, int length, ByteAdresse start, Align alignment, Config config) {
this.bezeichner = bezeichner;
this.inhalt = StringUtils.repeat(" ", length);
this.length = toByteLength(length);
this.byteAdresse = start.byteValue();
this.ausrichtung = alignment.getCode();
this.config = config;
}
/**
* Instantiates a new feld.
*
* @param name
* the name
* @param length
* the length
* @param start
* the start
* @param c
* the c
* @param alignment
* the alignment
* @deprecated durch entsprechenden Constructor mit ByteAdresse ersetzt
* (TODO: wird mit v8 entsorgt)
*/
@Deprecated
public Feld(final String name, final int length, final int start, final char c, final Align alignment) {
this(Bezeichner.of(name), length, start, alignment);
this.setInhalt(c);
}
/**
* Instantiates a new feld.
*
* @param name
* the name
* @param length
* the length
* @param start
* the start
* @param s
* the s
* @param alignment
* the alignment
* @deprecated durch entsprechenden Constructor mit Bezeichner und ByteAdresse ersetzt
* (TODO: wird mit v8 entsorgt)
*/
@Deprecated
public Feld(final String name, final int length, final int start, final String s, final Align alignment) {
this(Bezeichner.of(name), length, start, alignment);
this.setInhalt(s);
}
/**
* Instantiates a new feld.
*
* @param name
* the name
* @param start
* the start
* @param c
* the c
* @deprecated durch entsprechenden Constructor mit Bezeichner und ByteAdresse ersetzt
* (TODO: wird mit v8 entsorgt)
*/
@Deprecated
public Feld(final String name, final int start, final char c) {
this(name, 1, start, c, Align.LEFT);
}
/**
* Instantiates a new feld.
*
* @param start
* the start
* @param s
* the s
* @param alignment
* the alignment
* @deprecated durch entsprechenden Constructor mit ByteAdresse ersetzt
* (TODO: wird mit v8 entsorgt)
*/
@Deprecated
public Feld(final int start, final String s, final Align alignment) {
this(ByteAdresse.of(start), s, alignment);
}
/**
* Legt ein neues Feld an.
*
* @param start Start-Adresse
* @param s Inhalt
* @param alignment Ausrichtung
* @since 7.0 (07-Jan-2024)
*/
public Feld(final ByteAdresse start, final String s, final Align alignment) {
this.inhalt = s;
this.length = toByteLength(s.length());
this.byteAdresse = start.byteValue();
this.ausrichtung = alignment.getCode();
this.bezeichner = createBezeichner();
this.config = Config.getInstance();
}
/**
* Dies ist der Copy-Constructor, mit dem man ein bestehendes Feld
* kopieren kann.
*
* @param other das originale Feld
*/
public Feld(final Feld other) {
this(other.getBezeichner(), other.getAnzahlBytes(), other.getByteAdresse(), other.getAusrichtung(), other.config);
this.setInhalt(other.getInhalt());
}
protected Feld(final Feld other, final Config cfg) {
this(other.getBezeichner(), other.getAnzahlBytes(), other.getByteAdresse(), other.getAusrichtung(), cfg);
this.setInhalt(other.getInhalt());
}
/**
* Liefert die Ausrichtung eines Feldes. Dies ist hauptsaechlich fuer
* alhpanumerische Felder interessant.
*
* @return linksbuendig oder rechtsbuendig
*/
public Align getAusrichtung() {
return Align.of(ausrichtung);
}
/**
* Hiermit kann man die Ausrichtung umstellen.
*
* @param alignment z.B. {@link Align#LEFT}
* @since 6.1
*/
public void setAusrichtung(Align alignment) {
this.ausrichtung = alignment.getCode();
}
private Bezeichner createBezeichner() {
return Bezeichner.of(this.getClass().getSimpleName() + "@" + Integer.toHexString(this.hashCode()));
}
/**
* Gets the bezeichnung.
*
* @return the bezeichnung
*/
public String getBezeichnung() {
return this.bezeichner.getName();
}
/**
* Liefert den Bezeichner eines Feldes zurueck.
*
* Vor 1.0 lieferte diese Methode einen "String" zurueck. Aus
* Konsistenz-Gruenden wurde die alte Implementierung in
* "GetBzeichnerAsString" umbenannt.
*
*
* @return den Bezeichner des Feldes
* @since 1.0
*/
public Bezeichner getBezeichner() {
return this.bezeichner;
}
/**
* Setzt den Inhalt. Hierueber kann auch ein Inhalt gesetzt werden, der
* nicht zum Datentyp passt (z.B. Buchstaben in einem {@link NumFeld},
* damit ein Import nicht beim ersten fehlerhaften Feld abbricht.
*
* Um festzustellen, ob ein Feld einen gueltigen Wert hat, kann die
* {@link #isValid()}-Methode verwendet werden.
*
*
* @param neuerInhalt der neue Inhalt
*/
public void setInhalt(final String neuerInhalt) {
int anzahlBytes = this.getAnzahlBytes();
String s = getValidator().verify(neuerInhalt, this);
s = config.getBool("gdv.feld.truncate") ? truncate(s) : s;
if (s.length() > anzahlBytes) {
throw new IllegalArgumentException("Feld " + this.getBezeichner() + ": Parameter \"" + s
+ "\" ist laenger als " + anzahlBytes + " Zeichen!");
}
if (s.length() != anzahlBytes) {
this.resetInhalt();
}
this.inhalt = pack(s);
}
protected String pack(String s) {
if (getAusrichtung() == Align.LEFT) {
return StringUtils.stripEnd(s, " ");
} else {
return StringUtils.stripStart(s, " ");
}
}
/**
* Schneidet einen zu langen String unabhaengig vom Alignment rechts ab.
*
* @param s String, der evtl. gekuerzt wird
* @return String der Laenge {@link #getAnzahlBytes()}
*/
protected String truncate(String s) {
if (s.length() <= getAnzahlBytes()) {
return s;
}
return s.substring(0, getAnzahlBytes());
}
/**
* Setzt den Inhalt mit der uebergebenen Zahl.
*
* @param n Zahl
* @since 5.0
*/
public void setInhalt(BigInteger n) {
setInhalt(n.toString());
}
/**
* Setzt den Inhalt aus der uebergebenen Zahl.
*
* @param n der neue Inhalt
*/
public void setInhalt(final BigDecimal n) {
this.setInhalt(n.toString());
}
/**
* Setzt den Inhalt aus der uebergebenen Zahl.
*
* @param n der neue Inhalt
*/
public void setInhalt(final long n) {
this.setInhalt(Long.toString(n));
}
/**
* Sets the inhalt.
*
* @param c
* the new inhalt
*/
@JsonIgnore
public void setInhalt(final char c) {
this.resetInhalt();
this.setInhalt(c, 0);
}
/**
* Sets the inhalt.
*
* @param c
* zu setzendes Zeichen
* @param i
* index, beginnend bei 0
*/
public void setInhalt(final char c, final int i) {
StringBuilder sb = new StringBuilder(this.getInhalt());
sb.setCharAt(i, c);
this.inhalt = sb.toString();
}
/**
* Liefert den Inhalt, so wie er im Record steht (ungetrimm't).
*
* @return den Inhalt
*/
public String getInhalt() {
String blanks = StringUtils.repeat(' ', this.getAnzahlBytes() - this.inhalt.length());
if (getAusrichtung() == Align.LEFT) {
return this.inhalt + blanks;
} else {
return blanks + inhalt;
}
}
/**
* Setzt das Feld und liefert es als Ergebnis zurueck.
*
* @param inhalt neuer Inhalt
* @return das gesetzte Feld
* @since 5.0
*/
public Feld withInhalt(String inhalt) {
this.setInhalt(inhalt);
return this;
}
/**
* Reset inhalt.
*/
public void resetInhalt() {
int anzahlBytes = this.getAnzahlBytes();
this.inhalt = StringUtils.repeat(" ", anzahlBytes);
}
/**
* Gets the anzahl bytes.
*
* @return the anzahl bytes
*/
public final int getAnzahlBytes() {
return ByteAdresse.of(length).intValue();
}
/**
* Gets the byte adresse.
*
* @return Byte-Adresse, beginnend bei 1
*/
public final int getByteAdresse() {
return ByteAdresse.of(this.byteAdresse).intValue();
}
/**
* Gets the end adresse.
*
* @return absolute End-Adresse
*/
public final int getEndAdresse() {
return this.getByteAdresse() + this.getAnzahlBytes() - 1;
}
/**
* Ueberprueft, ob sich zwei Felder mit unterschiedlichen Start-Adressen ueberlagern.
*
* @param other
* das andere Feld
* @return true, falls sich die Felder ueberlappen
*/
public final boolean overlapsWith(final Feld other) {
if (this.byteAdresse == other.byteAdresse) {
return false;
}
if (this.byteAdresse < other.byteAdresse) {
return this.getEndAdresse() >= other.getByteAdresse();
}
return other.getEndAdresse() >= this.getByteAdresse();
}
/**
* Write.
*
* @param writer
* the writer
*
* @throws IOException
* Signals that an I/O exception has occurred.
*/
public final void write(final Writer writer) throws IOException {
writer.write(this.inhalt);
}
/**
* Checks if is empty.
*
* @return true, if is empty
*/
public boolean isEmpty() {
return StringUtils.isBlank(this.getInhalt());
}
/**
* Dient zum Ermittel, ob ein Werte schon gesetzt wurde. Dabei werden
* typische Initialisierungswerte wie "0" als "nicht gesetzt"
* interpretiert.
*
* @return true, falls Feld mit einem Wert belegt ist
* @since 3.1
*/
public boolean hasValue() {
String value = getInhalt();
return StringUtils.isNotBlank((this.getAusrichtung().compareTo(Align.RIGHT) == 0) ? StringUtils.replaceChars(value, '0', ' ') : value);
}
/**
* Valid bedeutet: Byte-Adresse >= 1, Feld geht nicht ueber
* (Teil-)Datensatz-Grenze hinaus, Ausrichtung ist bekannt.
*
* Aus Performance-Gruenden stuetzt sich diese Methode nicht direkt auf
* validate(), sondern implementiert die entsprechenden Abfragen selbst
* und bricht ab, wenn er etwas ungueltiges findet.
*
*
* @return false, falls Verletzung erkannt wird
* @since 0.1.0
*/
public boolean isValid() {
if (this.getByteAdresse() < 1) {
return false;
}
if (this.getEndAdresse() > 256) {
return false;
}
if (this.getAusrichtung() == Align.UNKNOWN) {
return false;
}
return this.validate().isEmpty();
}
/**
* Checks if is invalid.
*
* @return true, if is invalid
*/
public boolean isInvalid() {
return !isValid();
}
/**
* Validate.
*
* @return eine Liste mit Constraint-Verletzungen
*/
public List validate() {
return validate(Config.LAX);
}
/**
* Validiert das Feld mit der angegebenen Konfiguration.
*
* @param validationConfig Konfiguration
* @return Liste mit Validierungsfehler
*/
public List validate(Config validationConfig) {
List violations = validateInvariants();
if (this.getEndAdresse() > 256) {
ConstraintViolation cv = new SimpleConstraintViolation(this + ": Endadresse ueberschritten", this,
this.getEndAdresse());
violations.add(cv);
}
try {
this.getValidator().validate(getInhalt(), validationConfig);
} catch (RuntimeException ex) {
ConstraintViolation cv = new SimpleConstraintViolation(this, ex);
violations.add(cv);
}
return violations;
}
private List validateInvariants() {
List violations = new ArrayList<>();
if (getAusrichtung() == Align.UNKNOWN) {
violations.add(new SimpleConstraintViolation("Ausrichtung darf nicht UNKNOWN sein", this,
this.ausrichtung));
}
return violations;
}
/**
* Diese Methode ist dafuer vorgesehen, das Feld als normalen String ausgeben zu koennen. Zahlen koennen so z.B. in
* der Form "123,45" ausgegeben werden, unter Beruecksichtigung der eingestellten "Locale".
*
* @return Inhalt des Feldes
* @since 0.5.1
*/
public String format() {
return this.getInhalt();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return toShortString() + ": \"" + this.getInhalt() + "\"";
}
public String toShortString() {
return this.getClass().getSimpleName() + " " + this.getBezeichner() + " (" + this.getByteAdresse() + "-"
+ this.getEndAdresse() + ")";
}
/**
* Zwei Felder sind gleich, wenn sie die gleiche Adresse und den gleichen
* Inhalt haben.
*
* ACHTUNG: Bis v5.1 wurde noch die Ausrichtung fuer die Gleichheit
* herangezogen. Ab v5.1 spielt dies aber keine Rolle mehr.
*
*
* @param obj das andere Feld
* @return true, wenn beide Felder gleich sind
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof Feld)) {
return false;
}
Feld other = (Feld) obj;
return this.bezeichner.equals(other.bezeichner) && this.getInhalt().equals(other.getInhalt())
&& (this.byteAdresse == other.byteAdresse);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return this.byteAdresse + this.getInhalt().hashCode();
}
/**
* Es gilt fuer Feld a und b: a < b, wenn die Start-Adresse von a vor b
* liegt.
*
* @param other das andere Feld
* @return 0 wenn beide Felder die gleiche Startadresse haben
*/
@Override
public final int compareTo(final Feld other) {
return this.byteAdresse - other.byteAdresse;
}
/**
* Hierueber kann der Validator zur Pruefung im Vorfeld
* geholt werden.
*
* @return aktuellen Validator
*/
@JsonIgnore
public Feld.Validator getValidator() {
return config.getValidatorFor(getClass());
}
/**
* Die clone-Methode hat gegenueber dem CopyConstructor
* {@link Feld#Feld(Feld)} den Vorteil, dass es den richtigen Typ fuer die
* abgeleiteten Klassen zurueckliefert.
*
* @return eine Kopie
*/
@SuppressWarnings("squid:S2975")
@Override
public Object clone() {
return new Feld(this);
}
/**
* Die Validierung von Werten wurde jetzt in einer eingenen Validator-
* Klasse zusammengefasst. Damit kann die Validierung auch unabhaengig
* von Feld-Klasse im Vorfeld eingesetzt werden, um Werte auf ihre
* Gueltigkeit pruefen zu koennen.
*
* @since 5.3
*/
public static class Validator implements SimpleValidator {
private final Config config;
public Validator() {
this(Config.LAX);
}
public Validator(Config config) {
this.config = config;
}
protected Config getConfig() {
return this.config;
}
/**
* Im Gegensatzu zur validate-Methode wird hier eine
* {@link IllegalArgumentException} ausgeloest und das
* betroffene Feld noch mit ausgegeben
*
* @param value Wert, der validiert werden soll
* @param validatedFeld Feld, das validiert wurde
* @return der Wert selber zur Weiterverarbeitung
*/
public String verify(String value, Feld validatedFeld) {
try {
return validate(value);
} catch (RuntimeException ex) {
throw new IllegalArgumentException(
String.format("%s: Wert '%s' ist nicht erlaubt (%s)", validatedFeld.toShortString(), value, ex.getMessage()), ex);
}
}
/**
* Dieser validate-Methode validiert nur bei enstsprechender
* Konfiguration.
*
* @param value Wert, der validiert werden soll
* @return der Wert selber zur Weiterverarbeitung
*/
@Override
public String validate(String value) {
return validate(value, config);
}
public String validate(String value, Config validationConfig) {
if (value == null) {
throw new ValidationException("null-Werte sind nicht erlaubt");
}
switch (validationConfig.getValidateMode()) {
case LAX:
return validateLax(value);
case STRICT:
return validateStrict(value);
default:
return value;
}
}
/**
* Dieser validate-Methode bietet eine Basis-Validierung fuer die
* Standard-Faelle.
*
* @param value Wert, der validiert werden soll
* @return der Wert selber zur Weiterverarbeitung
*/
protected String validateLax(String value) {
LOG.debug("Inhalt von '{}' wird validiert.", value);
if (!Text.of(value).isPrintable()) {
throw new ValidationException(String.format("Text '%s' enthaelt ungueltige Zeichen", value));
}
return value;
}
/**
* Dieser validate-Methode validiert strenger und kann von Unterklassen
* ueberschrieben werden.
*
* @param value Wert, der validiert werden soll
* @return der Wert selber zur Weiterverarbeitung
*/
protected String validateStrict(String value) {
return validateLax(value);
}
}
}