gdv.xport.feld.Feld.java Source code

Java tutorial

Introduction

Here is the source code for gdv.xport.feld.Feld.java

Source

/*
 * $Id$
 *
 * Copyright (c) 2009 - 2012 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. (ob@aosd.de)
 */

package gdv.xport.feld;

import gdv.xport.annotation.FeldInfo;
import gdv.xport.config.Config;
import gdv.xport.satz.feld.FeldX;

import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.util.List;

import net.sf.oval.ConstraintViolation;
import net.sf.oval.Validator;
import net.sf.oval.constraint.Min;
import net.sf.oval.constraint.NotEqual;
import net.sf.oval.constraint.SizeCheck;
import net.sf.oval.context.ClassContext;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * 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<Feld>, Cloneable {

    private static final Logger LOG = LogManager.getLogger(Feld.class);
    /** statt "null". */
    public static final Feld NULL_FELD = new Feld();
    /** optional: Name des Felds. */
    private final Bezeichner bezeichner;
    private final Enum<?> bezeichnerEnum;
    private final StringBuilder inhalt;
    /** Achtung - die ByteAdresse beginnt bei 1 und geht bis 256. */
    @Min(1)
    private final int byteAdresse;
    /** Ausrichtung: rechts- oder linksbuendig. */
    @NotEqual("UNKNOWN")
    private final Align ausrichtung;

    /**
     * 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(FeldX.UNBEKANNT);
    }

    /**
     * Legt ein neues Feld an. Die Informationen dazu werden aus der
     * uebergebenen Enum bezogen.
     *
     * @param feldX Enum mit den Feldinformationen
     * @since 0.9
     */
    public Feld(final Enum<?> feldX) {
        this(feldX, getFeldInfo(feldX));
    }

    /**
     * Kreiert ein neues Feld.
     *
     * @param feldX der entsprechende Aufzaehlungstyp
     * @param info Annotation mit den Feldinformationen
     * @since 0.6
     */
    public Feld(final Enum<?> feldX, final FeldInfo info) {
        this.bezeichnerEnum = feldX;
        this.bezeichner = Feld.getAsBezeichner(feldX);
        this.byteAdresse = info.byteAdresse();
        this.ausrichtung = getAlignmentFrom(info);
        this.inhalt = new StringBuilder(info.anzahlBytes());
        for (int i = 0; i < info.anzahlBytes(); i++) {
            this.inhalt.append(' ');
        }
        this.setInhalt(info.value());
    }

    /**
     * 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(name, 1, s, alignment);
    }

    /**
     * Instantiates a new feld.
     *
     * @param name the name
     * @param start Start-Adresse
     * @param s der Inhalt
     * @param alignment the alignment
     */
    public Feld(final String name, final int start, final String s, final Align alignment) {
        this.bezeichner = new Bezeichner(name);
        this.inhalt = new StringBuilder(s);
        this.byteAdresse = start;
        this.ausrichtung = alignment;
        this.bezeichnerEnum = FeldX.UNBEKANNT;
    }

    /**
     * Erzeugt ein neues Feld.
     * <p>
     * TODO: Bitte nicht mehr benutzen - wird in 1.2 entfernt!
     * </p>
     *
     * @param name der Name
     * @param length die Anzahl der Bytes
     * @param start die Start-Adresse
     * @param alignment die Ausrichtung
     * @deprecated durch {@link #Feld(Bezeichner, int, int, Align)} abgeloest
     */
    @Deprecated
    public Feld(final String name, final int length, final int start, final Align alignment) {
        this(new Bezeichner(name), length, start, alignment);
    }

    /**
     * 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
     */
    public Feld(final Bezeichner bezeichner, final int length, final int start, final Align alignment) {
        this.bezeichner = bezeichner;
        this.inhalt = getEmptyStringBuilder(length);
        this.byteAdresse = start;
        this.ausrichtung = alignment;
        this.bezeichnerEnum = FeldX.UNBEKANNT;
    }

    /**
     * Instantiates a new feld.
     *
     * @param name
     *            the name
     * @param length
     *            the length
     * @param start
     *            the start
     * @param c
     *            the c
     * @param alignment
     *            the alignment
     */
    public Feld(final String name, final int length, final int start, final char c, final Align alignment) {
        this(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
     */
    public Feld(final String name, final int length, final int start, final String s, final Align alignment) {
        this(name, length, start, alignment);
        this.setInhalt(s);
    }

    /**
     * Instantiates a new feld.
     *
     * @param name
     *            the name
     * @param start
     *            the start
     * @param c
     *            the c
     */
    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
     */
    public Feld(final int start, final String s, final Align alignment) {
        this.inhalt = new StringBuilder(s);
        this.byteAdresse = start;
        this.ausrichtung = alignment;
        this.bezeichner = createBezeichner();
        this.bezeichnerEnum = FeldX.UNBEKANNT;
    }

    /**
     * Instantiates a new feld.
     *
     * @param length
     *            the length
     * @param alignment
     *            the alignment
     */
    public Feld(final int length, final Align alignment) {
        this(length, 1, alignment);
    }

    /**
     * Instantiates a new feld.
     *
     * @param length
     *            the length
     * @param start
     *            the start
     * @param alignment
     *            the alignment
     */
    public Feld(final int length, final int start, final Align alignment) {
        this.inhalt = getEmptyStringBuilder(length);
        this.byteAdresse = start;
        this.ausrichtung = alignment;
        this.bezeichner = createBezeichner();
        this.bezeichnerEnum = FeldX.UNBEKANNT;
    }

    /**
     * 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.ausrichtung);
        this.setInhalt(other.getInhalt());
    }

    private static StringBuilder getEmptyStringBuilder(final int length) {
        StringBuilder sbuf = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            sbuf.append(' ');
        }
        return sbuf;
    }

    /**
     * Die Default-Ausrichtung ist links-buendig. Diese Vorgabe kann aber von den Unterklassen ueberschrieben werde.
     *
     * @return links-buendig
     */
    protected Align getDefaultAlignment() {
        return Align.LEFT;
    }

    private Align getAlignmentFrom(final FeldInfo info) {
        assert info.align() != null;
        if (info.align() == Align.UNKNOWN) {
            return this.getDefaultAlignment();
        }
        return info.align();
    }

    private Bezeichner createBezeichner() {
        return new Bezeichner(this.getClass().getSimpleName() + "@" + Integer.toHexString(this.hashCode()));
    }

    /**
     * Legt das gewuenschte Feld an, das sich aus der uebergebenen Annotation
     * ergibt (Factory-Methode). Der Name wird dabei aus dem uebergebenen
     * Enum-Feld abgeleitet.
     *
     * @param feldX Enum fuer das erzeugte Feld
     * @param info die FeldInfo-Annotation mit dem gewuenschten Datentyp
     * @return das erzeugte Feld
     */
    public static Feld createFeld(final Enum<?> feldX, final FeldInfo info) {
        try {
            Constructor<? extends Feld> ctor = info.type().getConstructor(Enum.class, FeldInfo.class);
            return ctor.newInstance(feldX, info);
        } catch (NoSuchMethodException ex) {
            throw new IllegalArgumentException(
                    "no constructor " + info.type().getSimpleName() + "(String, FeldInfo) found", ex);
        } catch (InstantiationException ex) {
            throw new IllegalArgumentException("can't instantiate " + info.type(), ex);
        } catch (IllegalAccessException ex) {
            throw new IllegalArgumentException("can't access ctor for " + info.type(), ex);
        } catch (InvocationTargetException ex) {
            throw new IllegalArgumentException(
                    "error invoking ctor for " + info.type() + " (" + ex.getTargetException() + ")", ex);
        }
    }

    /**
     * Gets the bezeichnung.
     *
     * @return the bezeichnung
     */
    public String getBezeichnung() {
        return this.bezeichner.getName();
    }

    /**
     * Im Gegensatz zur Bezeichnung, die aus mehreren Woertern in Gross- und
     * Kleinbuchstaben bestehen kann, steht der Bezeichner nur aus einem Wort
     * (in Grossbuchstaben). Er wird aus der Bezeichnung unter Zuhilfenahme der
     * {@link Bezeichner}-Klasse ermittelt, wenn das bezeichner-Attribute nicht
     * gesetzt (bzw. UNBEKANNT) ist.
     * <p>
     * In 1.0 wurde die Methode in "getBezeichnerAsString" umbenannt, um die
     * Verwirrung mit der Bezeichner-Klasse nicht zu gross werden zu lassen.
     * Vorher hiess diese Klasse "getBezeichner", lieferte aber einen String
     * zurueck.
     * </p>
     *
     * @return Bezeichner in Grossbuchstaben
     * @since 0.6 (vor 1.0 hiess diese Methode "getBezeichner")
     * @deprecated durch {@link #getBezeichner()} ersetzt
     */
    @Deprecated
    public String getBezeichnerAsString() {
        if (!this.bezeichnerEnum.equals(FeldX.UNBEKANNT)) {
            return this.bezeichnerEnum.name();
        }
        try {
            Field field = Bezeichner.getField(this.bezeichner.getName());
            return field.getName();
        } catch (IllegalArgumentException iae) {
            LOG.info("\"" + this.bezeichner + "\" not found in " + Bezeichner.class + ":", iae);
            return this.bezeichner.getName().replaceAll(" ", "_").toUpperCase();
        }
    }

    /**
     * Liefert den Bezeichner eines Feldes zurueck.
     * <p>
     * Vor 1.0 lieferte diese Methode einen "String" zurueck. Aus
     * Konsistenz-Gruenden wurde die alte Implementierung in
     * "GetBzeichnerAsString" umbenannt.
     * </p>
     *
     * @return den Bezeichner des Feldes
     * @since 1.0
     */
    public Bezeichner getBezeichner() {
        return this.bezeichner;
    }

    /**
     * Sets the inhalt.
     *
     * @param s
     *            the new inhalt
     */
    public final void setInhalt(final String s) {
        int anzahlBytes = this.getAnzahlBytes();
        if (s.length() > anzahlBytes) {
            throw new IllegalArgumentException("Feld " + this.getBezeichner() + ": Parameter \"" + s
                    + "\" ist laenger als " + anzahlBytes + " Zeichen!");
        }
        this.resetInhalt();
        switch (this.ausrichtung) {
        case LEFT:
            this.inhalt.replace(0, s.length(), s);
            break;
        case RIGHT:
            int l = s.length();
            int start = anzahlBytes - l;
            this.inhalt.replace(start, start + l, s);
            break;
        default:
            throw new IllegalStateException("object was not properly initialized");
        }
    }

    /**
     * Sets the inhalt.
     *
     * @param n
     *            the new inhalt
     */
    public void setInhalt(final int n) {
        this.setInhalt(Integer.toString(n));
    }

    /**
     * Sets the inhalt.
     *
     * @param c
     *            the new inhalt
     */
    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) {
        this.inhalt.setCharAt(i, c);
    }

    /**
     * Gets the inhalt.
     *
     * @return the inhalt
     */
    public String getInhalt() {
        return this.inhalt.toString();
    }

    /**
     * Reset inhalt.
     */
    public void resetInhalt() {
        int anzahlBytes = this.getAnzahlBytes();
        for (int i = 0; i < anzahlBytes; i++) {
            this.inhalt.setCharAt(i, ' ');
        }
    }

    /**
     * Wenn sich das Feld vergroessert, werden rechts Leerzeichen aufgefuellt (alphanumerische Zeichen sind
     * standardmaessig linksbuendig).
     *
     * @param n
     *            neue Groesse
     */
    public void setAnzahlBytes(final int n) {
        assert this.inhalt.length() <= n : "drohender Datenverlust";
        for (int i = this.inhalt.length(); i < n; i++) {
            this.inhalt.append(' ');
        }
    }

    /**
     * Gets the anzahl bytes.
     *
     * @return the anzahl bytes
     */
    public final int getAnzahlBytes() {
        return this.inhalt.length();
    }

    /**
     * Gets the byte adresse.
     *
     * @return Byte-Adresse, beginnend bei 1
     */
    public final int getByteAdresse() {
        return this.byteAdresse;
    }

    /**
     * Gets the end adresse.
     *
     * @return absolute End-Adresse
     */
    public final int getEndAdresse() {
        return this.byteAdresse + 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.byteAdresse;
        }
        return other.getEndAdresse() >= this.byteAdresse;
    }

    /**
     * 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.toString());
    }

    /**
     * Checks if is empty.
     *
     * @return true, if is empty
     */
    public boolean isEmpty() {
        return StringUtils.isBlank(this.getInhalt());
    }

    /**
     * Valid bedeutet: Byte-Adresse >= 1, Feld geht nicht ueber
     * (Teil-)Datensatz-Grenze hinaus, Ausrichtung ist bekannt.
     * <p>
     * Aus Performance-Gruenden stuetzt sich diese Methode nicht auf validate(),
     * sondern implementiert die entsprechenden Abfragen selbst.
     * </p>
     *
     * @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.ausrichtung == Align.UNKNOWN) {
            return false;
        }
        return true;
    }

    /**
     * Checks if is invalid.
     *
     * @return true, if is invalid
     */
    public boolean isInvalid() {
        return !isValid();
    }

    /**
     * Validate.
     *
     * @return the list< constraint violation>
     */
    public List<ConstraintViolation> validate() {
        Validator validator = new Validator();
        List<ConstraintViolation> violations = validator.validate(this);
        if (this.getEndAdresse() > 256) {
            ConstraintViolation cv = new ConstraintViolation(new SizeCheck(), this + ": boundary exceeded", this,
                    this.getEndAdresse(), new ClassContext(this.getClass()));
            violations.add(cv);
        }
        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 this.getClass().getSimpleName() + " " + this.getBezeichner() + "(" + this.byteAdresse + "-"
                + this.getEndAdresse() + "): \"" + this.getInhalt().trim() + "\"";
    }

    /**
     * Zwei Felder sind gleich, wenn sie die gleiche Adresse und den gleichen
     * Inhalt haben.
     *
     * @param obj das andere Feld
     * @return true, wenn beide Felder gleich sind
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public final 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) && this.ausrichtung == other.ausrichtung;
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#hashCode()
     */
    @Override
    public final 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;
    }

    /**
     * Liefert die uebergebene Enum-Konstante als Bezeichner zurueck. Dazu
     * verwendet es die {@link Bezeichner}-Klasse, um festzustellen, ob es den
     * Namen schon als Konstante dort gibt.
     *
     * @param feldX die Enum-Konstante
     * @return den Bezeichner
     * @since 1.0
     */
    public static Bezeichner getAsBezeichner(final Enum<?> feldX) {
        Object object = getAsObject(feldX);
        if (object instanceof Bezeichner) {
            return (Bezeichner) object;
        }
        return new Bezeichner((String) object);
    }

    /**
     * Liefert den Namen als Bezeichner zurueck. Dazu verwendet es die
     * {@link Bezeichner}-Klasse, um festzustellen, ob es den Namen schon als
     * Bezeichner gibt. Falls nicht, wird der Name zurueckgeliefert.
     *
     * @param feldX das Feld-Element mit dem gesuchten Bezeichner
     * @return z.B. "Inkassoart"
     */
    public static String getAsBezeichnung(final Enum<?> feldX) {
        return (String) getAsObject(feldX);
    }

    private static Object getAsObject(final Enum<?> feldX) {
        try {
            Field field = Bezeichner.class.getField(feldX.name());
            return field.get(null);
        } catch (NoSuchFieldException ex) {
            LOG.info("Bezeichner." + feldX.name() + " not found:", ex);
        } catch (IllegalArgumentException ex) {
            LOG.warn(ex);
        } catch (IllegalAccessException ex) {
            LOG.warn("can't access Bezeichner." + feldX.name(), ex);
        }
        return toBezeichnung(feldX);
    }

    /**
     * Konvertiert einen Bezeichner (in GROSSBUCHSTABEN) in die entsprechende Bezeichnung.
     *
     * @param name
     *            z.B. HELLO_WORLD (als Aufzaehlungstyp)
     * @return z.B. "Hello World"
     */
    public static String toBezeichnung(final Enum<?> name) {
        FeldInfo feldInfo = getFeldInfo(name);
        if ((feldInfo == null) || StringUtils.isEmpty(feldInfo.bezeichnung())) {
            return toBezeichnung(name.name());
        } else {
            return feldInfo.bezeichnung();
        }
    }

    private static String toBezeichnung(final String name) {
        String converted = name.replaceAll("_", " ");
        ByteBuffer outputBuffer = Config.DEFAULT_ENCODING.encode(converted);
        String convertedISO = new String(outputBuffer.array(), Config.DEFAULT_ENCODING);
        return WordUtils.capitalize(convertedISO.toLowerCase());
    }

    /**
     * Ermittelt die FeldInfo aus dem uebergebenen Enum.
     *
     * @param feldX the feld x
     * @return the feld info
     */
    protected static FeldInfo getFeldInfo(final Enum<?> feldX) {
        try {
            Field field = feldX.getClass().getField(feldX.name());
            return field.getAnnotation(FeldInfo.class);
        } catch (NoSuchFieldException nsfe) {
            throw new InternalError("no field " + feldX + " (" + nsfe + ")");
        }
    }

    /**
     * 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
     */
    @Override
    public Object clone() {
        return new Feld(this);
    }

}