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

Java tutorial

Introduction

Here is the source code for com.prowidesoftware.swift.model.Tag.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.io.Serializable;
import java.util.logging.Level;

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

import com.prowidesoftware.swift.model.field.Field;

/**
 * Representation of a swift tag (also 'field' in computer terms) in a message.
 * 
 * <h4>NOTE</h4>
 * This class is also used inside SwiftBlock to store the value of a block that 
 * is not associated with a particular field, in this case, the Tag name attribute 
 * is set to <code>null</code>;
 * <h4>IMPORTANT NOTE ABOUT MULTILINE TAGS</h4>
 * <p>Multiline tags are read with readline, so while swift defines \r\n to be the
 * line terminator, the parser accepts a more wide range of line terminations. The 
 * values are stored in swift line terminators, that is always \r\n, so be aware that 
 * if a multiline field is separated with \n parse will be successfully but the returned
 * value will be separated by \r\n</p>
 * 
 * <p>Instances of this class may have a list of unparsed texts (UnparsedTextList).
 * For easy access, methods have been created that first ensure the lists exists (the
 * real object is created and then call the base method).<br />
 * However, not all the base list methods have been implemented. If you need to use not
 * exposed functionality, retrieve the underlying list with (see getUnparsedTexts method)</p>
 *  
 * @author www.prowidesoftware.com
 */
public class Tag implements Serializable {
    private static final transient java.util.logging.Logger log = java.util.logging.Logger
            .getLogger(Tag.class.getName());

    private static final long serialVersionUID = -1066430327311949399L;

    /**
     * Unique identified when this tag is a persisted element
     */
    protected Long id;

    /**
     * Indicates the position of this tag in a message when persisted.
     * This value is used to remember the positions of the tags inside 
     * a block when persisted. This value may not be set when persistence
     * is not used and should not be used by clients.
     */
    protected Integer sortKey;

    /**
     * Name of the tag, usually a number that may be followed by a letter.
     * This value may be <code>null</code>.
     */
    protected String name;

    /**
     * Value of the corresponding tag.
     */
    protected String value;

    /**
     * List of unparsed texts. For performance reasons, this will be null until really needed.
     */
    protected UnparsedTextList unparsedTexts = null;

    /**
     * Reference to the sequence node, if any, that this tags belongs to.
     */
    protected SequenceNode sequence = null;

    /**
     * Default constructor
     */
    public Tag() {
    }

    /**
     * Create a tag from the value in inner.
     * If inner contains one : character, the 
     * string before is set as the tagname and the rest as the value.
     * If inner contains more than one :, then the first value is used
     * as previously described.
     * If no : is contained, then all string is put in value and name remains <code>null</code> (useful for bloc data)
     * 
     * <br/>
     * Maps:
     * <code><pre>
     * "" -> name=null, value=null
     * "foo" -> name=null, value=foo
     * ":foo" -> name=null, value=foo
     * "foo:" -> name=foo, value=null
     * "foo:bar" -> name=foo, value=bar
     * </pre></code>
     * @param inner the string to build the tag
     * @throws IllegalArgumentException if inner is <code>null</code>
     */
    public Tag(String inner) {

        // sanity check
        Validate.notNull(inner, "parameter 'inner' cannot be null");

        // analyze how to split the inner value
        int i = inner.indexOf(':');
        if (i >= 0) {
            if (i > 0) {
                this.name = inner.substring(0, i);
            }
            if (i + 1 < inner.length()) {
                this.value = inner.substring(i + 1);
            }
        } else {
            if (inner.length() > 0)
                this.value = inner;
        }
    }

    /**
     * Create a tag with the given tagname and value
     * @param tagname name of this tag
     * @param value the value of this tag
     * @throws IllegalArgumentException if parameter tagname or value are <code>null</code>
     */
    public Tag(String tagname, String value) {

        // sanity check
        Validate.notNull(tagname, "parameter 'tagname' cannot be null");
        Validate.notNull(value, "parameter 'value' cannot be null");

        this.name = tagname;
        this.value = value;
    }

    /**
     * Constructor for an unparsed text list
     * @param unparsedText the list of unparsed texts
     * @see Tag#Tag()
     */
    public Tag(UnparsedTextList unparsedText) {

        // base constructor
        this();

        // set the unparsed text list
        this.unparsedTexts = unparsedText;
    }

    /**
     * Constructor for tag encoded value and an unparsed text list
     * @param inner the string to build the tag
     * @param unparsedText the list of unparsed texts
     * @throws IllegalArgumentException if parameter inner is <code>null</code>
     * @see Tag#Tag(String)
     */
    public Tag(String inner, UnparsedTextList unparsedText) {

        // base constructor
        this(inner);

        // set the unparsed text list
        this.unparsedTexts = unparsedText;
    }

    /**
     * Constructor for tag name and value and an unparsed text list
     * @param tagname name of this tag
     * @param value the value of this tag
     * @param unparsedText the list of unparsed texts
     * @throws IllegalArgumentException if parameter tagname or value are <code>null</code>
     * @see Tag#Tag(String,String)
     */
    public Tag(String tagname, String value, UnparsedTextList unparsedText) {

        // base constructor
        this(tagname, value);

        // set the unparsed text list
        this.unparsedTexts = unparsedText;
    }

    /**
     * Get the name of this tag
     * @return a string with the current tag name
     */
    public String getName() {
        return name;
    }

    /**
     * Set the tag name
     * @param name the name of the tag to be set
     * @throws IllegalArgumentException if parameter name is <code>null</code>
     */
    public void setName(String name) {

        // sanity check
        Validate.notNull(name, "parameter 'name' cannot be null");

        this.name = name;
    }

    /**
     * Get the value of the tag.<br> 
     * Notice that in some cases the value can be null,
     * for example the value of the "DLM" tag in this block:<br> 
     * {5:{CHK:F9351591947F}{SYS:1610010606VNDZBET2AXXX0019000381}{DLM:}}
     *
     * 
     * @return a string with the value of the tag or <code>null</code> if the value was not set
     */
    //TODO review parser implementation and check if always <code>null</code> is set or empty string
    public String getValue() {
        return value;
    }

    /**
     * Sets the value of this tag.
     * @param value the value for the tag, may be <code>null</code>
     */
    public void setValue(String value) {
        this.value = value;
    }

    public String toString() {
        return new StringBuilder().append("Tag[").append(name).append(":").append(value).append("]").toString();
    }

    /**
     * Get the unique identifier of the tag if it is persisted
     * @return the unique id or <code>null</code> if it is not a persistent object
     */
    public Long getId() {
        return id;
    }

    /**
     * Set the unique identifier of the tag if it is persisted
     * @param id the id to be set
     * @see #sortKey
     */
    public void setId(Long id) {

        this.id = id;
    }

    /**
     * get the sortkey of this tag
     * @return an integer with the current sortkey
     * @see #sortKey
     */
    public Integer getSortKey() {
        return sortKey;
    }

    /**
     * Set the sortkey of this tag.
     * This value may be changed by clients when persistence is used and the order of the tags
     * in a message are being modified.
     * @param sortKey the new sortkey
     */
    public void setSortKey(Integer sortKey) {
        this.sortKey = sortKey;
    }

    /**
     * verifies that the unparsed text list exists
     */
    protected void unparsedTextVerify() {
        if (this.unparsedTexts == null)
            this.unparsedTexts = new UnparsedTextList();
    }

    /**
     * returns the unparsed text list
     * @return the unparsed text attached to this tag object
     */
    public UnparsedTextList getUnparsedTexts() {

        // create the list if needed
        unparsedTextVerify();
        return (this.unparsedTexts);
    }

    /**
     * sets the list of unparsed texts
     * @param texts the new list of unparsed texts (may be null)
     */
    public void setUnparsedTexts(UnparsedTextList texts) {

        this.unparsedTexts = texts;
    }

    /**
     * returns the size of the unparsed text list
     * @return the count of unparsed texts attached to this tag object
     */
    public Integer getUnparsedTextsSize() {

        // no list => size is zero...
        if (this.unparsedTexts == null)
            return new Integer(0);
        return this.unparsedTexts.size();
    }

    /**
     * decides if a specific text (by index) is likely a SWIFT FIN message. Exceptions are inherited from
     * base implementation methods.
     * @param index the unparsed text number
     * @return true if the unparsed text at position index is a full SWIFT message
     * @throws IllegalArgumentException if parameter index is <code>null</code>
     * @throws IndexOutOfBoundsException if parameter index is out of bounds
     */
    public Boolean unparsedTextIsMessage(Integer index) {

        // create the list if needed
        unparsedTextVerify();
        return (this.unparsedTexts.isMessage(index));
    }

    /**
     * get an unparsed text
     * @param index the unparsed text number
     * @return the requested text
     * @throws IllegalArgumentException if parameter index is <code>null</code>
     * @throws IndexOutOfBoundsException if parameter index is out of bounds
     */
    public String unparsedTextGetText(Integer index) {

        // create the list if needed
        unparsedTextVerify();
        return (this.unparsedTexts.getText(index));
    }

    /**
     * get an unparsed text as a parsed swift message
     * @param index the unparsed text number
     * @return the unparsed text at position index parsed into a SwiftMessage object
     * @throws IllegalArgumentException if parameter index is <code>null</code> 
     */
    public SwiftMessage unparsedTextGetAsMessage(Integer index) {

        // create the list if needed
        unparsedTextVerify();
        return (this.unparsedTexts.getTextAsMessage(index));
    }

    /**
     * adds a new unparsed text
     * @param text the unparsed text to append
     * @throws IllegalArgumentException if parameter text is <code>null</code> 
     */
    public void unparsedTextAddText(String text) {

        // create the list if needed
        unparsedTextVerify();
        this.unparsedTexts.addText(text);
    }

    /**
     * adds a new unparsed text from a message
     * @param message the message to be appended
     * @throws IllegalArgumentException if parameter message is <code>null</code> 
     */
    public void unparsedTextAddText(SwiftMessage message) {

        // create the list if needed
        unparsedTextVerify();
        this.unparsedTexts.addText(message);
    }

    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + ((sortKey == null) ? 0 : sortKey.hashCode());
        result = prime * result + ((unparsedTexts == null) ? 0 : unparsedTexts.hashCode());
        result = prime * result + ((value == null) ? 0 : value.hashCode());
        return result;
    }

    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final Tag other = (Tag) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        if (sortKey == null) {
            if (other.sortKey != null)
                return false;
        } else if (!sortKey.equals(other.sortKey))
            return false;
        if (unparsedTexts == null) {
            if (other.unparsedTexts != null)
                return false;
        } else if (!unparsedTexts.equals(other.unparsedTexts))
            return false;
        if (value == null) {
            if (other.value != null)
                return false;
        } else if (!value.equals(other.value))
            return false;
        return true;
    }

    /**
     * Attempt to parse the tag name as an integer.
     * <em>Note that for 15A this method will return -1</em>
     * 
     * @return the resulting value of parsing the tagname as an integer or -1 if an error occurs
     */
    public int getNameAsInt() {
        try {
            return Integer.valueOf(this.name).intValue();
        } catch (Exception unused) {
            return -1;
        }
    }

    /**
     * Tells if this tagname is a given number, so the integer 58 will match 58A and 58D.
     * 
     * @param n the number that this tagname will be compared to
     * @return <code>true</code> if this tagname starts with the given number or <code>false</code> in any other case
     */
    public boolean isNumber(int n) {
        return this.name != null && getNumber() != null && getNumber().equals(n);
    }

    /**
     * Iterate the current tagname and return only number as told by {@link Character#isDigit(char)}
     * 
     * @return an integer containing the numeric of the tagname or <code>null</code> if no digits are found
     * @since 6.2
     */
    public Integer getNumber() {
        if (this.name != null) {
            final StringBuilder sb = new StringBuilder();
            for (int i = 0; i < this.name.length(); i++) {
                char c = this.name.charAt(i);
                if (Character.isDigit(c)) {
                    sb.append(c);
                }
            }
            if (sb.length() > 0) {
                return Integer.parseInt(sb.toString());
            }
        }
        return null;
    }

    /**
     * Iterate the current tagname and return only letters as told by {@link Character#isLetter(char)}
     * 
     * @return a string containing only letter characters of the tagname or <code>null</code> if no letters are found
     */
    public String getLetterOption() {
        if (this.name != null) {
            final StringBuilder sb = new StringBuilder();
            for (int i = 0; i < this.name.length(); i++) {
                char c = this.name.charAt(i);
                if (Character.isLetter(c)) {
                    sb.append(c);
                }
            }
            if (sb.length() > 0) {
                return sb.toString();
            }
        }
        return null;
    }

    /**
     * @see Field#getField(Tag)
     */
    public Field getField() {
        return Field.getField(this);
    }

    /**
     * Tell if this tag value contains any of the given values.
     * This method is case sensitive. It handles null values.
     * Actual test is delegated to {@link StringUtils#contains(String,String)}
     * @param values variable list of values to test
     * @return <code>true</code> if the value of this tag is one of the given values. 
     * returns <code>false</code> in any other case, including a null or empty list of values
     */
    public boolean contains(String... values) {
        if (values != null && values.length > 0) {
            final String _v = getValue();
            for (String v : values) {
                if (StringUtils.contains(_v, v)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Shorthand equivalent to calling first {@link #setName(String)} and then {@link #setValue(String)}
     * @param name the tagname
     * @param value the tagvalue
     */
    public void setNameValue(String name, String value) {
        setName(name);
        setValue(value);
    }

    /**
     * equivalent to StringUtils.startsWith(tag.getValue(), prefix)
     * @see StringUtils#startsWith(String, String)
     */
    public boolean startsWith(String prefix) {
        return StringUtils.startsWith(getValue(), prefix);
    }

    /**
     * equivalent to StringUtils.contains(tag.getValue(), searchStr)
     * @see StringUtils#contains(String, String)
     */
    public boolean contains(String searchStr) {
        return StringUtils.contains(getValue(), searchStr);
    }

    /**
     * 
     * @return this Tag as a FieldNN instance or <code>null</code> if an error occurs
     */
    public Field asField() {
        try {
            return Field.getField(this);
        } catch (Exception e) {
            log.log(Level.WARNING, "cannot build Field object for Tag " + this, e);
        }
        return null;
    }

}