com.prowidesoftware.swift.model.SwiftMessageUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.prowidesoftware.swift.model.SwiftMessageUtils.java

Source

/*******************************************************************************
 * Copyright (c) 2016 Prowide Inc.
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as 
 *     published by the Free Software Foundation, either version 3 of the 
 *     License, or (at your option) any later version.
 *
 *     This program 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.  
 *     
 *     Check the LGPL at <http://www.gnu.org/licenses/> for more details.
 *******************************************************************************/
package com.prowidesoftware.swift.model;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;

import com.prowidesoftware.swift.WifeException;
import com.prowidesoftware.swift.model.field.CurrencyContainer;
import com.prowidesoftware.swift.model.field.DateContainer;
import com.prowidesoftware.swift.model.field.Field;
import com.prowidesoftware.swift.model.mt.AbstractMT;

/**
 * Utility methods that provide higher level access to {@link SwiftMessage}
 *
 * @author www.prowidesoftware.com
 * @since 6.0
 */
public class SwiftMessageUtils {
    private static final Logger log = Logger.getLogger(SwiftMessageUtils.class.getName());
    private final SwiftMessage msg;

    public SwiftMessageUtils() {
        this(null);
    }

    public SwiftMessageUtils(final SwiftMessage m) {
        this.msg = m;
    }

    public List<String> currencyStrings() {
        return SwiftMessageUtils.currencyStrings(msg);
    }

    /**
     * Mirrors logic on {@link CurrencyContainer#currencyStrings()} including all fields
     * @param m
     * @return an empty list if none found
     */
    public static List<String> currencyStrings(final SwiftMessage m) {
        if (m != null) {
            final SwiftBlock4 b4 = m.getBlock4();
            if (b4 != null && !b4.isEmpty()) {
                final ArrayList<String> curs = new ArrayList<String>();
                for (final Tag t : b4.getTags()) {
                    final Field f = t.getField();
                    if (f instanceof CurrencyContainer) {
                        final CurrencyContainer cc = (CurrencyContainer) f;
                        curs.addAll(cc.currencyStrings());
                    }
                }
                return curs;
            }
        }
        return Collections.emptyList();
    }

    /**
     * @see #valueDate(SwiftMessage)
     */
    public Calendar valueDate() {
        return SwiftMessageUtils.valueDate(msg);
    }

    /**
     * Iterates through the parameter tags and removes all inner blocks enclosed between
     * sequences boundary fields 16R and 16S
     * <br />
     * This method requires a sequence starting with 16R and ending with 16S, so first
     * and last tags must be those. Due to this constraint, null, empty and sequences with less 
     * than 3 tags will be returned without any modification.
     *
     * @param sequence a block with a sequence to filter
     * @return a new block containing all tags that are outside a 16R/S block, the only 16R/S tags returned are the first and last delimiters.
     * @throws IllegalArgumentException if the starting tag is not 16R or the ending tag is not the matching 16S
     * @since 7.8.1
     */
    public static SwiftTagListBlock removeInnerSequences(final SwiftTagListBlock sequence) {
        if (sequence == null || sequence.size() < 3) {
            return sequence;
        }
        final Tag start = sequence.getTag(0);
        final Tag end = sequence.getTag(sequence.size() - 1);
        if (!StringUtils.equals("16R", start.getName())) {
            throw new IllegalArgumentException(
                    "Starting tag of sequence must be 16R (and was " + start.getName() + ")");
        }
        if (!StringUtils.equals("16S", end.getName())) {
            throw new IllegalArgumentException(
                    "Ending tag of sequence must be 16S (and was " + end.getName() + ")");
        }
        if (!StringUtils.equals(start.getValue(), end.getValue())) {
            throw new IllegalArgumentException("Qualifier of last 16S (" + end.getValue()
                    + ") must match the qualifier of the starting (" + start.getValue() + ") 16R tag");
        }
        final SwiftTagListBlock result = new SwiftTagListBlock();
        String qualifier = null;
        for (int i = 0; i < sequence.getTags().size(); i++) {
            final Tag t = sequence.getTags().get(i);
            if (i > 0 && qualifier == null && StringUtils.equals(t.getName(), "16R")) {
                /*
                 * found sequence start
                 */
                qualifier = t.getValue();
            } else if (qualifier != null && StringUtils.equals(t.getName(), "16S")
                    && StringUtils.equals(t.getValue(), qualifier)) {
                /*
                 * found sequence end
                 */
                qualifier = null;
            } else if (qualifier == null) {
                result.append(t);
            }
        }
        return result;
    }

    /**
     * Gets the value date of a message.<br /><br />
     *
     * The value date is meaningful and defined by the standard only for a subset of message types.
     * In most of the cases it is contained in the date subfield of field 32A (for example MT103)
     * or field 30 (for example MT101).<br />
     *
     * Notice a lot of messages do not define a value date.<br />
     *
     * Also a few define several fields as value date, or the value date can be repeated.
     * for those messages the first one is returned as follows:<br />
     * <ul>
     * <li>For MT450 returns the first value date occurrence of field 32A</li>
     * <li>For MT455 returns the value date from field 32A (not from 33[C,D])</li>
     * <li>For MT456 returns the first value date occurrence of field 33D</li>
     * <li>For MT564 returns the value date from Cash Movements Field 98a with qualifier PAYD (not qualifier VALU)</li>
     * </ul>
     *
     * @param m the message where the value date is to be found
     * @return found date or <code>null</code> if the message does not defines a value date, or if the defined value date field is not present in the message
     * @since 7.7
     */
    public static Calendar valueDate(final SwiftMessage m) {
        if (m != null) {
            final SwiftBlock4 b4 = m.getBlock4();
            if (b4 != null && !b4.isEmpty()) {
                Tag t = null;
                Field f = null;
                if (m.isType(101, 104, 107, 201, 203, 204, 207, 210)) {
                    t = b4.getTagByName("30");
                } else if (m.isType(102, 103, 200, 202, 205, 400, 450, 455, 800, 802, 900, 910)) {
                    t = b4.getTagByName("32A");
                } else if (m.isType(456)) {
                    t = b4.getTagByName("33D");
                } else if (m.isType(513)) {
                    final SwiftTagListBlock seq = b4.getSubBlock("ORDRDET");
                    if (seq != null) {
                        f = seq.getFieldByNumber(98, "SETT");
                    }
                } else if (m.isType(514, 515, 518)) {
                    final SwiftTagListBlock seq = b4.getSubBlock("CONFDET");
                    if (seq != null) {
                        f = seq.getFieldByNumber(98, "SETT");
                    }
                } else if (m.isType(541, 543, 545, 547)) {
                    final SwiftTagListBlock seq = b4.getSubBlock("TRADDET");
                    if (seq != null) {
                        f = seq.getFieldByNumber(98, "SETT");
                    }
                } else if (m.isType(564)) {
                    final SwiftTagListBlock seq = b4.getSubBlock("CASHMOVE");
                    if (seq != null) {
                        f = seq.getFieldByNumber(98, "PAYD");
                    }
                } else if (m.isType(566)) {
                    final SwiftTagListBlock seq = b4.getSubBlock("CASHMOVE");
                    if (seq != null) {
                        f = seq.getFieldByNumber(98, "POST");
                    }
                } else if (m.isType(730, 768, 769)) {
                    t = b4.getTagByName("32D");
                } else if (m.isType(734, 752, 756)) {
                    t = b4.getTagByName("33A");
                } else if (m.isType(742, 754)) {
                    t = b4.getTagByName("34A");
                }
                if (t != null) {
                    f = t.getField();
                }
                if (f != null && f instanceof DateContainer) {
                    return ((DateContainer) f).dates().get(0);
                }
            }
        }
        return null;
    }

    /**
     * Proprietary checksum for message integrity verification.
     * Please notice this is not the SWIFT trailer CHK field.
     * @param model
     * @return always null (this method is not yet implemented)
     */
    public static String calculateChecksum(final SwiftMessage model) {
        //TODO missing implementation
        return null;
    }

    /**
     * Split the given message by the field 15, returning the letter option in the field 15 as the key in the map.
     * @see #splitByField15(SwiftTagListBlock)
     */
    public static Map<String, SwiftTagListBlock> splitByField15(final SwiftMessage msg) {
        if (msg != null && msg.getBlock4() != null) {
            return splitByField15(msg.getBlock4());
        } else {
            return new HashMap<String, SwiftTagListBlock>();
        }
    }

    /**
     * Split the given block content by the field 15, returning the letter option in the field 15 as the key in the map
     *
     * @param block the content to split
     * @return a map with letter options as keys, and blocks as value
     * @since 7.7
     */
    public static Map<String, SwiftTagListBlock> splitByField15(final SwiftTagListBlock block) {
        final Map<String, SwiftTagListBlock> result = new HashMap<String, SwiftTagListBlock>();
        if (block != null) {
            SwiftTagListBlock currentList = null;
            for (final Tag t : block.getTags()) {
                if (t.getNumber() == 15) {
                    final String letter = t.getLetterOption();
                    if (letter != null && letter.length() == 1) {
                        final SwiftTagListBlock thisList = new SwiftTagListBlock();
                        result.put(letter, thisList);
                        currentList = thisList;
                    }
                }
                if (currentList != null) {
                    currentList.append(t);
                }
            }
        }
        return result;
    }

    /**
     * Helper method to retrieve all sequences starting with 15X where X is the letterOption parameter
     * @since 7.7
     * @see #splitByField15(SwiftTagListBlock, String)
     */
    public static List<SwiftTagListBlock> splitByField15(final SwiftMessage msg, final String letterOption) {
        if (msg != null && msg.getBlock4() != null) {
            return splitByField15(msg.getBlock4(), letterOption);
        } else {
            return Collections.emptyList();
        }
    }

    /**
     * Helper method to retrieve all sequences starting with 15X where X is the letterOption parameter.
     * Field 15a is used as a boundary for sequences, so the letter option correspond to a subsequence name.
     * @param block the content to split into subsequences
     * @param letterOption a letter option for the field boundary
     * @return found subsequences or an empty list if field 15 is not found
     * @since 7.7
     */
    public static List<SwiftTagListBlock> splitByField15(final SwiftTagListBlock block, final String letterOption) {
        Validate.notNull(letterOption);
        Validate.isTrue(StringUtils.length(letterOption) == 1, "letter option must be only one character");
        final List<SwiftTagListBlock> result = new ArrayList<SwiftTagListBlock>();
        if (block != null) {
            SwiftTagListBlock currentList = null;
            for (final Tag t : block.getTags()) {
                if (t.getNumber() == 15) {
                    final String letter = t.getLetterOption();
                    if (letter != null && letter.length() == 1) {
                        if (letter.equals(letterOption)) {
                            final SwiftTagListBlock thisList = new SwiftTagListBlock();
                            result.add(thisList);
                            currentList = thisList;
                        } else {
                            currentList = null;
                        }
                    }
                }
                if (currentList != null) {
                    currentList.append(t);
                }
            }
        }
        return result;
    }

    /**
     * Gets the message reference from field 20 (if present) 
     * or from field 20C if message category is 5.
     * @param m the message where the reference is to be found
     * @return found reference or <code>null</code> if the message does not defines a reference, or if the defined reference field is not present in the message
     * @since 7.8
     */
    public static String reference(final SwiftMessage m) {
        if (m != null) {
            final SwiftBlock4 b4 = m.getBlock4();
            if (b4 != null && !b4.isEmpty()) {
                final Tag t = b4.getTagByName("20");
                if (t != null) {
                    return t.getValue();
                }
                if (m.getType() != null && m.getType().startsWith("5")) {
                    final Field f = b4.getFieldByNumber(20, "SEME");
                    if (f != null) {
                        return f.getComponent(2);
                    }
                }
            }
        }
        return null;
    }

    /**
     * Joins all the given sequences in one single list.
     *  
     * @param sequences the sequences to be joined. Can be <code>null</code> or empty, in which case this method returns {@link SwiftTagListBlock#EMPTY_LIST}
     * @return a single {@link SwiftTagListBlock} containing all elements in order of each of the given sequences or {@link SwiftTagListBlock#EMPTY_LIST} if sequences is null or empty
     * @since 7.8
     */
    public static SwiftTagListBlock join(final List<? extends SwiftTagListBlock> sequences) {
        if (sequences == null || sequences.isEmpty())
            return SwiftTagListBlock.EMPTY_LIST;

        final SwiftTagListBlock result = new SwiftTagListBlock();
        for (final SwiftTagListBlock b : sequences) {
            result.getTags().addAll(b.getTags());
        }
        return result;
    }

    /**
     * Creates a sequence  and all it's containing parents.
     * This method is mainly useful for writing test cases. Instead of writing:
     * <pre><code>
     * MT535.SequenceB.newInstance(
     *       MT535.SequenceB1b.newInstance(
     *          MT535.SequenceB1b.newInstance(
     *             MT535.SequenceB1b1.newInstance(
     *                tags
     *             )
     *          )
     *       )
     * );
     * </code></pre>
     * This method is the same with a much cleaner code literature:
     * <pre><code>
     *    SwiftMessageUtils.createSequenceWithParents(MT535.class, "B1b1", tags);
     * </code></pre>
     * 
     * <em>Note:</em><br>
     * Using  
     * <pre><code>
     *    SwiftMessageUtils.createSequenceWithParents(MT535.class, "B", tags);
     * </code></pre>
     * Is virtually the same as:
     * <pre><code>
     *    MT535.SequenceB.newInstance(tags);
     * </code></pre>
     * 
     * @param mt the MT class for which the sequence is to be created
     * @param sequenceName name of the sequence
     * @param tags the content to put in the sequence
     * @return the SwiftTagListBlock containing all parent sequences, the sequence requested and the contents
     * @since 7.8
     */
    public static final SwiftTagListBlock createSubsequenceWithParents(final Class<? extends AbstractMT> mt,
            final String sequenceName, final Tag... tags) {
        log.finer("Create sequence " + sequenceName);
        final SwiftTagListBlock result = new SwiftTagListBlock();
        result.append(tags);

        for (int i = sequenceName.length(); i >= 1; i--) {
            final String sn = StringUtils.substring(sequenceName, 0, i);
            final SwiftTagListBlock newresult = createSequenceSingle(mt, sn, result.asTagArray());
            log.finer(sn + " => " + newresult);
            result.setTags(newresult.getTags());
        }
        return result;

    }

    public static SwiftTagListBlock createSequenceSingle(final Class<? extends AbstractMT> mt,
            final String sequenceName, final Tag... tags) {
        final String cn = mt.getName() + "$Sequence" + sequenceName;
        try {
            final Class<?> subSequenceClass = Class.forName(cn);
            final Method method = subSequenceClass.getMethod("newInstance", Tag[].class);
            return (SwiftTagListBlock) method.invoke(null, new Object[] { tags });
        } catch (Exception e) {
            log.log(Level.WARNING, "Reflection error: mt=" + mt.getName() + ", sequenceName=" + sequenceName
                    + ", tags=" + tags + " - " + e, e);
            throw new WifeException("Reflection error: mt=" + mt.getName() + ", sequenceName=" + sequenceName
                    + ", tags=" + tags + " - " + e);
        }
    }
}