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

org.kapott.hbci.protocol.MultipleSyntaxElements Maven / Gradle / Ivy

Go to download

HBCI4j - Home Banking Computer Interface for Java - Clone from https://github.com/hbci4j/hbci4java

There is a newer version: 3.5.46
Show newest version
/*  $Id: MultipleSyntaxElements.java,v 1.2 2012/03/06 23:18:26 willuhn Exp $

    This file is part of HBCI4Java
    Copyright (C) 2001-2008  Stefan Palme

    HBCI4Java is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    HBCI4Java is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

package org.kapott.hbci.protocol;

import lombok.extern.slf4j.Slf4j;
import org.kapott.hbci.exceptions.NoValueGivenException;
import org.kapott.hbci.exceptions.ParseErrorException;
import org.kapott.hbci.exceptions.PredelimErrorException;
import org.kapott.hbci.exceptions.TooMuchElementsException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

/* die child-elemente von strukturierten syntaxelementen (msg, seg, sg, deg)
    werden nicht direkt als listenelemente der uebergeordneten
    syntaxelemente abgelegt. statt dessen wird fuer jedes child-element ein
    syntaxelementarray angelegt, die wiederum das oder die childelement(e)
    enthaelt. hintergrund ist der, dass ein childelement (z.b. eine
    deg ), in einem uebergeordneten element (z.b. einem segment) mehr
    als einmal auftreten kann (wenn in der syntaxbeschreibung das anzahl feld
    werte groesser 1 enthaelt). da die in der syntaxbeschreibung angegegeben
    restriktionen fuer dieses syntaxelement fuer jedes auftreten desselben gelten,
    wird die tatsache der "beliebigen haeufigkeit" in dem syntaxelementarray
    gekapselt jedes syntaxelement enthaelt also eine liste seiner child-elemente,
    wobei jedes listenelement ein syntaxelementarray (mit dem ein- oder mehrmaligen
    auftreten des eigentlichen syntaxelementes) ist. ein syntaxelementarray
    enthaelt eine menge (1 oder mehr) von syntaxelementen des gleichen typs */
@Slf4j
public abstract class MultipleSyntaxElements {
    private List elements;
    private String path;
    private String name;
    private String type;
    private int minnum;
    private int maxnum;
    private int syntaxIdx; // die Position dieses Container innerhalb
    // der Syntax-Definition des Eltern-Elementes
    private Document document;
    private Node ref;
    private SyntaxElement parent;

    /**
     * anlegen eines neuen syntaxelementarrays fuer ein syntaxelement;
     * ref ist eine xml-node-referenz auf das syntaxelement
     */
    protected MultipleSyntaxElements(Node ref, String path, Document document) {
        initData(ref, path, document);
    }

    /**
     * anlegen einer neuen syntaxelementlist beim parsen des strings res;
     * - ref ist dabei die referenz auf einen xml-node, der das
     * syntaxelement festlegt, fuer den die syntaxelementlist erzeugt werden soll;
     * - predefs siehe SyntaxElement()
     * - predelim0 ist der delimiter, der vor dem ersten element innerhalb dieser
     * syntaxelementlist auftreten muesste;
     * - predelim1 ist der delimiter, der vor dem zweiten, dritten, usw. element in der
     * syntaxelementlist auftreten muesste (der unterschied zwischen predelim0 und
     * predelim1 ist der, dass predelim0 evtl. von uebergeordneten elementen
     * propagiert wird (z.b. wenn die syntaxelementlist selbst das erste syntaxelement
     * einer msg repraesentiert), predelim1 ist allerdings immer der delimiter,
     * der fuer das aktuell uebergeordnete syntaxelement zu verwenden ist)
     */
    protected MultipleSyntaxElements(Node ref, String path, char predelim0, char predelim1, StringBuilder res,
                                     int fullResLen, Document document, Map predefs,
                                     Map valids) {
        initData(ref, path, predelim0, predelim1, res, fullResLen, document, predefs, valids);
    }

    /**
     * erzeugt einen neuen eintrag in der elements liste; dabei wird ein
     * syntaxelement erzeugt, das im xml-node ref referenziert wird;
     * idx ist die indexnummer des zu erzeugenden syntaxelementes
     * innerhalb der elementlist
     */
    protected abstract SyntaxElement createAndAppendNewElement(Node ref, String path, int idx, Document document);

    /**
     * siehe SyntaxElement::parseElementList()
     */
    protected abstract SyntaxElement parseAndAppendNewElement(Node ref, String path, char predelim, int idx,
                                                              StringBuilder res, int fullResLen, Document document,
                                                              Map predefs, Map valids);

    private void initData(Node ref, String path, Document document) {
        type = ((Element) ref).getAttribute("type");
        name = ((Element) ref).getAttribute("name");
        if (name.length() == 0) {
            name = type;
        }

        this.elements = new ArrayList<>();
        this.parent = null;
        this.syntaxIdx = -1;
        this.ref = ref;
        this.document = document;

        StringBuilder temppath = new StringBuilder(128);
        if (path != null && path.length() != 0)
            temppath.append(path).append(".");
        temppath.append(name);
        this.path = temppath.toString();

        String st;

        minnum = 1;
        st = ((Element) ref).getAttribute("minnum");
        if (st.length() != 0)
            minnum = Integer.parseInt(st);

        maxnum = 1;
        st = ((Element) ref).getAttribute("maxnum");
        if (st.length() != 0)
            maxnum = Integer.parseInt(st);

        try {
            // anlegen mindestens eines syntaxelementes
            // warum? würde es nicht auch reichen, NUR den container anzulegen,
            // wenn minnum=0 ist? -- nein, es gibt zu viele "optionale" Elemente,
            // die in Wirklichkeit gar nicht optional sind, aber mit der Option
            // DONT_TRY_TO_CREATE erzeugt werden, so dass sie also nicht angelegt
            // werden würden und somit fehlerhafte Nachrichten die Folge wären.
            SyntaxElement child = createAndAppendNewElement(ref, path, 0, document);
            if (child != null)
                child.setParent(this);

            /* erzeugen sovieler syntaxelemente, bis die mindestanzahl
             aus der syntaxdefinition erreicht ist */
            for (int i = 1; i < minnum; i++) {
                child = createAndAppendNewElement(ref, path, i, document);
                if (child != null)
                    child.setParent(this);
            }
        } catch (RuntimeException e) {
            throw e;
        }
    }

    protected void init(Node ref, String path, Document document) {
        initData(ref, path, document);
    }

    /**
     * siehe SyntaxElement::propagateValue()
     */
    protected boolean propagateValue(String destPath, String value, boolean tryToCreate, boolean allowOverwrite) {
        boolean ret = false;

        if (tryToCreate) {
            int destPathLen = destPath.length();
            int pathLen = path.length();

            if (destPath.startsWith(path) &&
                destPathLen > pathLen &&
                destPath.charAt(pathLen) == '_') {

                int nextDot = destPath.indexOf(".", pathLen);
                if (nextDot == -1)
                    nextDot = destPathLen;
                int number = Integer.parseInt(destPath.substring(pathLen + 1, nextDot));

                if (number > elements.size()) {
                    String temppath = path.substring(0, path.lastIndexOf("."));

                    for (int i = elements.size(); i < number; i++) {
                        SyntaxElement child = createAndAppendNewElement(ref, temppath, i, document);
                        if (child != null)
                            child.setParent(this);
                    }
                }
            }
        }

        for (SyntaxElement e : elements) {
            String ePath = e.getPath();
            if (destPath.equals(ePath) || destPath.startsWith(ePath + ".")) {
                if (e.propagateValue(destPath, value, tryToCreate, allowOverwrite)) {
                    ret = true;
                    break;
                }
            }
        }

        return ret;
    }

    public SyntaxElement getParent() {
        return parent;
    }

    public void setParent(SyntaxElement parent) {
        this.parent = parent;
    }

    int getSyntaxIdx() {
        return this.syntaxIdx;
    }

    void setSyntaxIdx(int syntaxIdx) {
        this.syntaxIdx = syntaxIdx;
    }

    protected boolean storeValidValueInDE(String destPath, String value) {
        boolean ret = false;

        for (SyntaxElement e : elements) {
            String ePath = e.getPath();
            if (destPath.equals(ePath) || destPath.startsWith(ePath + ".")) {
                if (e.storeValidValueInDE(destPath, value)) {
                    ret = true;
                }
                break;
            }
        }

        return ret;
    }

    /**
     * siehe SyntaxElement::getValue()
     */
    protected String getValueOfDE(String path) {
        String ret = null;

        for (SyntaxElement e : elements) {
            String ePath = e.getPath();
            if (path.equals(ePath) || path.startsWith(ePath + ".")) {
                ret = e.getValueOfDE(path);
                break;
            }
        }

        return ret;
    }

    protected String getValueOfDE(String path, int zero) {
        String ret = null;

        for (SyntaxElement e : elements) {
            String ePath = e.getPath();
            if (path.equals(ePath) || path.startsWith(ePath + ".")) {
                ret = e.getValueOfDE(path, 0);
                break;
            }
        }

        return ret;
    }

    SyntaxElement getElement(String path) {
        SyntaxElement ret = null;

        for (SyntaxElement e : elements) {
            String ePath = e.getPath();
            if (path.equals(ePath) || path.startsWith(ePath + ".")) {
                ret = e.getElement(path);
                break;
            }
        }

        return ret;
    }

    protected void validateOneElement(SyntaxElement elem, int idx) {
        try {
            elem.validate();
        } catch (NoValueGivenException e) {
            if (idx < minnum || idx + 1 < elements.size()) {
                throw e;
            }
        }

        if (maxnum > 0 && idx >= maxnum)
            throw new TooMuchElementsException(getPath(), idx);
    }

    protected void validate() {
        int idx = 0;
        for (SyntaxElement e : elements) {
            validateOneElement(e, idx++);
        }
    }

    void addElement(SyntaxElement x) {
        elements.add(x);
    }

    public List getElements() {
        return elements;
    }

    public String getPath() {
        return path;
    }

    protected String getName() {
        return name;
    }

    protected String getType() {
        return type;
    }

    // ---------------------------------------------------------------------------------------------------------------

    int enumerateSegs(int startValue, boolean allowOverwrite) {
        int idx = startValue;

        for (SyntaxElement s : getElements()) {
            if (s != null)
                idx = s.enumerateSegs(idx, allowOverwrite);
        }

        return idx;
    }

    private void initData(Node ref, String path, char predelim0, char predelim1, StringBuilder res, int fullResLen,
                          Document document,
                          Map predefs, Map valids) {
        this.ref = null;
        this.document = null;
        this.syntaxIdx = -1;
        this.elements = new ArrayList<>();
        this.type = ((Element) ref).getAttribute("type");
        this.name = ((Element) ref).getAttribute("name");
        if (name.length() == 0) {
            this.name = this.type;
        }
        this.parent = null;

        StringBuilder temppath = new StringBuilder(128);
        if (path != null && path.length() != 0)
            temppath.append(path).append(".");
        temppath.append(name);
        this.path = temppath.toString();

        String st;

        minnum = 1;
        st = ((Element) ref).getAttribute("minnum");
        if (st.length() != 0)
            minnum = Integer.parseInt(st);

        maxnum = 1;
        st = ((Element) ref).getAttribute("maxnum");
        if (st.length() != 0)
            maxnum = Integer.parseInt(st);

        int idx = 0;
        boolean ready = false;

        try {
            while (!ready) {
                // sichern des reststrings
                StringBuilder save = new StringBuilder(res.toString());
                boolean emptyElementFound = false;

                try {
                    // versuch, ein weiteres syntaxelement zu erzeugen
                    SyntaxElement child = parseAndAppendNewElement(ref, path, (idx == 0) ? predelim0 : predelim1, idx
                        , res, fullResLen, document, predefs, valids);
                    if (child != null)
                        child.setParent(this);
                } catch (ParseErrorException e) {

                    // [willuhn 2012-03-06, BUG 1129] weiterwerfen, wenn sie als fatal eingestuft ist
                    if (e.isFatal())
                        throw e;

                    // wenn das nicht klappt, dann reststring zuruecksetzen, aber nur,
                    //   wenn naechstes zeichen nicht wieder ein delimiter ist
                    //   dann war naemlich das zu generierende DE leer!!!

                    // charAt(0) ist auf jede fall ein delimiter.
                    // wenn auch charAt(1) ein delimiter ist, dann ist
                    // das dazwischenliegende syntaxelement leer; in diesem
                    // fall muss der vorderste delimiter entfernt werden
                    // (so dass es so aussieht, als wurde das leere syntaxelement
                    // irgendwie richtig geparst)

                    // die exception kann entweder durch einen document-fehler oder durch
                    // ein leeres element (was ein spezieller fall eines document-fehlers ist)
                    // ausgeloest worden sein.
                    // da das entfernen von leeren elementen optional ist (und manchmal sogar
                    // sinvollerweise gar nicht stattfindet), muessen die exceptions, die wegen
                    // leerer elemente geworfen wurden, als OK akzeptiert werden, solange die
                    // mindestanzahl bereits gefuellter elemente erreicht ist

                    if (save.length() > 1) {

                        //////////////////////////////////////////////////////////////////////
                        // Dirty-Hack fuer Sonderfall optionale MultipleDEGs. Also eine Liste vieler DEGs,
                        // die alle optional sein koennen. bei den "AllowedGV" koennen das 999 sein.
                        // Wir wuerden hier jedes einzeln parsen - nur um festzustellen, dass
                        // nichts drin steht und wir ein AllowedGV-DEG ohne GV-Code erzeugen,
                        // weil da eigentlich nur noch sowas steht: "++++++++++++++++++++ usw".
                        // Das verlaengert das Parsen enorm. Wir kuerzen daher hier ab.
                        // Wenn this ein MultiDEGs mit minnum = 0 und maxnum > 1 ist, dann checken
                        // wir, ob auf dem Substring von "save(0,maxnum-idx)" nur noch "+"-Zeichen
                        // kommen. Wenn das der Fall ist, koennen wir hier abbrechen
                        // Siehe http://www.onlinebanking-forum.de/forum/topic.php?t=19879&page=last#last_post
                        if ((this instanceof MultipleDEGs) && this.minnum == 0 && this.maxnum > 1 && idx > 1) {
                            int size = this.maxnum - idx;
                            if (size > save.length())
                                size = save.length();
                            String rest = save.substring(0, size);
                            if (containsOnly(rest, '+')) {
                                log.debug("applying shortcut for optional MultipleDEGs, have no more content in " +
                                    "according range");
                                res.replace(0, res.length(), res.substring(size - 1)); // Wir schneiden die "+++++..
                                // ." alle weg
                                ready = true;
                                continue;
                            }
                        }
                        //////////////////////////////////////////////////////////////////////

                        char secondChar = save.charAt(1);

                        if (secondChar == '+' || secondChar == ':' || secondChar == '\'') {
                            // nur wenn der Fehler nicht durch einen predelimiter-error
                            // verursacht wurde, darf der delimiter (der also offensichtlich richtig
                            // und erwartet war) geloescht werden
                            if (!(e instanceof PredelimErrorException)) {
                                save.deleteCharAt(0);
                            }

                            emptyElementFound = true;
                        }
                    } else {
                        emptyElementFound = true;
                    }

                    res.replace(0, res.length(), save.toString());

                    /* wenn bisher weniger als die mindestanzahl geklappt hat,
                     dann exception werfen */
                    if (idx < minnum)
                        throw new ParseErrorException("reststring in " + getPath() + ": " + res.toString(), e);

                    // es wird nur dann aufgehoert, weitere elemente dem aktuellen container hinzu-
                    // zufuegen, wenn ein element gefunden wurde, was offentsichlich nicht mehr dazu-
                    // gehoert (exception, aber nicht leeres element) --> dann stimmt naemlich entweder
                    // der predelimiter nicht, oder die document ist falsch
                    if (!emptyElementFound) {
                        ready = true;
                    }
                }

                // anlegen eines neuen synaxelementes hat geklappt, bzw.
                // ein FEHLER beim anlegen kann aufgrund der gegebenen constraints
                // akzeptiert werden (leeres element, aber mindestanzahl erreicht)
                idx++;

                /* wenn die maxanzahl erreicht wurde oder
                 wenn die maxanzahl nicht definiert ist, aber kein neues element
                 erzeugt werden konnte (wenn gesicherter reststring und tatsaechlicher
                 reststring gleich sind; minnum ist aber erreicht),
                 dann diesen container normal beenden */
                if ((maxnum != 0 && idx >= maxnum) ||
                    (maxnum == 0 && save.toString().equals(res.toString()) && !emptyElementFound)) {
                    ready = true;
                }
            }
        } catch (RuntimeException e) {
            throw e;
        }
    }

    protected void init(Node ref, String path, char predelim0, char predelim1, StringBuilder res, int fullResLen,
                        Document document,
                        Map predefs, Map valids) {
        initData(ref, path, predelim0, predelim1, res, fullResLen, document, predefs, valids);
    }

    /**
     * siehe SyntaxElement.fillValues()
     */
    protected void extractValues(Map values) {
        for (SyntaxElement element : elements) {
            element.extractValues(values);
        }
    }

    int checkSegSeq(int value) {
        for (SyntaxElement element : elements) {
            value = element.checkSegSeq(value);
        }

        return value;
    }

    public String toString(int zero) {
        return toString();
    }

    public void getElementPaths(Map p, int[] segref, int[] degref, int[] deref) {
    }

    /**
     * Prueft, ob der Text s nur aus dem Zeichen c besteht.
     *
     * @param s der Text.
     * @param c das Zeichen.
     * @return true, wenn der Text nur dieses Zeichen enthaelt.
     */
    private boolean containsOnly(String s, char c) {
        for (char c2 : s.toCharArray()) {
            if (c != c2)
                return false;
        }

        return true;
    }

    protected void destroy() {
        elements.clear();
        elements = null;
        name = null;
        parent = null;
        path = null;
        ref = null;
        document = null;
        type = null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy