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

com.prowidesoftware.swift.model.field.Narrative Maven / Gradle / Ivy

There is a newer version: SRU2024-10.2.3
Show newest version
/*
 * Copyright 2006-2023 Prowide
 *
 * 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 or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.prowidesoftware.swift.model.field;

import com.prowidesoftware.swift.io.writer.FINWriterVisitor;
import com.prowidesoftware.swift.utils.LineWrapper;
import java.math.BigDecimal;
import java.util.*;
import org.apache.commons.lang3.StringUtils;

/**
 * Models the value of fields containing narrative content. The content is normally text wrapped in lines but they also
 * support the narrative to be structured with codewords.
 *
 * 

When the structured option is used, different line formats are supported depending on the actual field. * In most of the fields the only element in the structured format is the actual text. However some fields can include * currency and amount (for example: 73A, 71D, 71B, 73), country codes (for example 77B) or the narrative partitioned * as a main narrative and a supplement (for example: 75, 76). * *

Supported line formats are: *

 * Format 1
 *      Line 1:         /8a/[additional information]               (Code)(Narrative)
 *      Lines 2-n:      /8a/[additional information]               (Code)(Narrative)
 *                      [//continuation of additional information] (Narrative)
 *
 * Format 2
 *      Line 1:         /8c/[additional information]                Code)(Narrative)
 *      Lines 2-n:      /8c/[additional information]               (Code)(Narrative)
 *                      [//continuation of additional information] (Narrative)
 *
 * Format 3
 *      Line 1:         /8c/[3!a13d][additional information]       (Code)(Currency)(Amount)(Narrative)
 *      Lines 2-6:      /8c/[3!a13d][additional information]       (Code)(Currency)(Amount)(Narrative)
 *                      [//continuation of additional information] (Narrative)
 *
 * Format 4
 *      Line 1:         /8c/[additional information]               (Code)(Narrative)
 *      Lines 2-3:      [//continuation of additional information] (Narrative)
 *      Variant for cat 1 with country
 *      Line 1:         /8c/2!a[//additional information]          (Code)(Country)(Narrative)
 *      Lines 2-3:      [//continuation of additional information] (Narrative)
 *
 * Format 5
 *      Line 1:         /2n/[supplement 1][/supplement2]           (Query Number)(Narrative 1)(Narrative 2)
 *      Lines 2-6       /2n/[supplement 1][/supplement2]
 *                      [//continuation of supplementary information]
 *
 * Format 6
 *      Line 1:         /6c/[additional information]               (Code)(Narrative)
 *      Lines 2-100:    /6c/[additional information]               (Code)(Narrative)
 *                      [continuation of additional information]   (Narrative) (cannot start with slash)
 *
 * Format 7
 *      Code between slashes at the beginning of a line
 *
 * Format 8
 *      Free format codes in slashes, not necessary on new lines
 * 
* *

This model is intended to be a generic container for any type of structured or unstructure narrative, for any * narrative container field. When a component (currency, amount, country or supplement) does not apply to a field, * it will be simply set to null by the parser. * * @since 9.0.1 */ public class Narrative { private List structured = new ArrayList<>(); private List unstructuredFragments = new ArrayList<>(); Narrative() {} private Narrative(Builder builder) { this.structured = builder.structured; this.unstructuredFragments = builder.unstructuredFragments; } /** * This builder allows creating the structured narrative with ease, just adding chunks of texts with optional * codewords and extra fields. It is intended to simplify construction of different line formats. * *

The implementation splits the text into fragments up to the given length, with word wrapping. *

Since the builder will do the line wrapping, the parameter text should not contain line breaks. If your text * is already formatted into lines use the {@link NarrativeContainer#appendLine(String)} method or the field plain * component setter when you have the compete formatted value. * * @param lineLength specific field max line length, used for text wrapping */ public static Builder builder(int lineLength) { return new Builder(lineLength); } /** * Gets the structured part of the narrative, meaning the segments composed by a codeword and a structured text. * See examples below. * *

For field: *

     * :77J:THREE (3) COMMERCIAL INVOICES
     * INSTEAD OF FIVE (5) PRESENTED
     * 
* the result will be empty since all the narrative content is unstructured. * *

For field: *

     * :72:/BNF/first line of beneficiary
     * //second line of beneficiary
     * supplementary unstructured data
     * 
* the result will be a single entry for the "BNF" content including the narrative texts "first line of beneficiary" * and "second line of beneficiary". Notice the "supplementary unstructured data" will not be part of the returned * structured data since it is not the continuation for "BNF" but some additional unstructured content in the last * field line. * * @return the structured content or empty if no structured content is present */ public List getStructured() { return structured; } /** * Gets the first structured narrative found for the given codeword. *

Notice codewords can be repeated for some fields, this method returns the first instance found. * * @param codeword an instruction codeword for the field such as: BNF, HOLD, NAME, PREVINST, etc.. * @return found structured content for the codeword or null if the field does not have structured narrative or * the structured narrative is not set for the codeword. */ public StructuredNarrative getStructured(String codeword) { if (this.structured != null) { for (StructuredNarrative structured : this.structured) { if (StringUtils.equals(codeword, structured.getCodeword())) { return structured; } } } return null; } /** * Count the occurrences of a given codeword in the structured narrative * * @param codeword an instruction codeword for the field such as: OCMT, CHGS, etc.. * @return number of structured content with the parameter codeword or zero if none is found */ public int countStructured(String codeword) { int count = 0; if (this.structured != null) { for (StructuredNarrative structured : this.structured) { if (StringUtils.equals(codeword, structured.getCodeword())) { count++; } } } return count; } Narrative add(StructuredNarrative structuredNarrative) { this.structured.add(structuredNarrative); return this; } /** * Returns the part of the field value that is not structured in codewords. * See examples below. * *

For field: *

     * :77J:THREE (3) COMMERCIAL INVOICES
     * INSTEAD OF FIVE (5) PRESENTED
     * 
* the result will be the complete field value since all the narrative content is unstructured. * *

For field: *

     * :72:/BNF/dbln-111- RED-1,123.456-78/10/
     * //05 redemption monies
     * supplementary unstructured data
     * 
* the result will be "supplementary unstructured data" since that line at the end is unstructured content, not * the continuation of the structured narrative for BNF codeword. * *

If the text is wrapped in lines the result list contains one element per line. * To get the unstructured narrative text as a simple joined string use {@link #getUnstructured(String)} * * @return the fragments of the unstructured text or an empty list if there is no unstructured text */ public List getUnstructuredFragments() { return unstructuredFragments; } /** * @see #getUnstructuredFragments() */ Narrative addUnstructuredFragment(String narrativeFragment) { this.unstructuredFragments.add(narrativeFragment); return this; } /** * Gets this unstructured content as a single String, meaning if the text was wrapped in lines this method will * return the joined lines without separator. * * @return the narrative fragments joined as a single string or null if the narrative does not have any text */ public String getUnstructured() { return getUnstructured(null); } /** * Gets this unstructured content as a single String, meaning if the text was wrapped in lines this method will * return the joined lines. * * @param delimiter optional delimiter, could be for example null, empty, space or line break. * @return the narrative fragments joined as a single string or null if the narrative does not have any text */ public String getUnstructured(String delimiter) { if (!this.unstructuredFragments.isEmpty()) { String s = delimiter != null ? delimiter : ""; return String.join(s, this.unstructuredFragments); } return null; } /** * @return true if non of the narrative fields are set */ public boolean isEmpty() { return this.structured.isEmpty() && this.unstructuredFragments.isEmpty(); } /** * Simple validation to check that either the structured or unstructured content is set * For the structured field the {@link StructuredNarrative#valid()} method is used. */ public boolean valid() { return !this.unstructuredFragments.isEmpty() || validStructured(); } private boolean validStructured() { for (StructuredNarrative structured : this.structured) { if (!structured.valid()) { return false; } } return true; } /** * If the field has structured narrative returns a set of all codewords used * * @return found codewords or empty if none is found (content is unstructured) */ public Set codewords() { Set result = new HashSet<>(); for (StructuredNarrative s : this.structured) { if (s.getCodeword() != null) { result.add(s.getCodeword()); } } return result; } /** * Serializes this narrative components into the single string value (SWIFT format) adding new lines as necessary */ public String getValue() { StringBuilder result = new StringBuilder(); // add the structured narrative for (StructuredNarrative structured : this.structured) { if (result.length() > 0) { // if not the first, add a line break result.append(FINWriterVisitor.SWIFT_EOL); } // append mandatory codeword, even if it is empty (invalid) we will add the slashes result.append("/") .append(StringUtils.trimToEmpty(structured.getCodeword())) .append("/"); if (structured.getCountry() != null) { // append the country (only used in some fields) result.append(structured.getCountry()); } else if (structured.getCurrency() != null || structured.getAmount() != null) { // append the currency and amount (only used in some fields) result.append(StringUtils.trimToEmpty(structured.getCurrency())); if (structured.getAmount() != null) { result.append(structured.getAmount()); } } // append the narrative content boolean first = true; for (String fragment : structured.getNarrativeFragments()) { if (!first) { // if this is not the first fragment we add a line break and the double slash to indicate this is // a continuation of the preceding information result.append(FINWriterVisitor.SWIFT_EOL).append("//"); } else if (structured.getCountry() != null) { // if in the first line and country was added, then the additional information must be separated // with double slash, in the same line though result.append("//"); } result.append(fragment); first = false; } // append the narrative supplement (only used in some fields) first = true; for (String fragment : structured.getNarrativeSupplementFragments()) { if (first) { // if in the first supplement we add a single slash separator result.append("/"); } else { // if this is not the first supplement fragment we add a line break and the double slash to // indicate this is a continuation of the preceding information result.append(FINWriterVisitor.SWIFT_EOL).append("//"); } result.append(fragment); first = false; } } // add the unstructured narrative at te end (one fragment per line with no separators) for (String fragment : this.unstructuredFragments) { if (result.length() > 0) { // if not the first, add a line break result.append(FINWriterVisitor.SWIFT_EOL); } result.append(fragment); } return result.toString(); } public static class Builder { final int lineLength; private final List structured = new ArrayList<>(); private final List unstructuredFragments = new ArrayList<>(); /** * @param lineLength specific field max line length, used for text wrapping */ public Builder(int lineLength) { this.lineLength = lineLength; } /** * Adds structured narrative content composed by codeword and wrapped text. * Will be serialized as: *

         * Line 1:      /codeword/[narrative]
         * Lines 2-n:   [//continuation of narrative]
         * 
* * @param codeword the codeword (instruction code) * @param narrative the narrative text (will be wrapped into lines if necessary) */ public Builder addCodeword(String codeword, String narrative) { StructuredNarrative structured = new StructuredNarrative().setCodeword(codeword); String prefix = "/" + StringUtils.trim(codeword) + "/"; for (String fragment : wrap(prefix, narrative)) { structured.addNarrativeFragment(fragment); } this.structured.add(structured); return this; } /** * Adds structured narrative content composed by codeword, currency, amount and optional wrapped text. * Will be serialized as: *
         * Line 1:      /codeword/[currency][amount][narrative]
         * Lines 2-n:   [//continuation of narrative]
         * 
* * @param codeword the codeword (instruction code) * @param currency a three letters ISO currency code * @param narrative the narrative text (will be wrapped into lines if necessary) */ public Builder addCodewordWithAmount(String codeword, String currency, BigDecimal amount, String narrative) { StructuredNarrative structured = new StructuredNarrative() .setCodeword(codeword) .setCurrency(currency) .setAmount(amount); String amountString = amount != null ? amount.toString() : ""; String prefix = "/" + StringUtils.trim(codeword) + "/" + StringUtils.trim(currency) + amountString; for (String fragment : wrap(prefix, narrative)) { structured.addNarrativeFragment(fragment); } this.structured.add(structured); return this; } /** * Adds structured narrative content composed by codeword, country code and optional wrapped text. * Will be serialized as: *
         * Line 1:      /codeword/country[//narrative]
         * Lines 2-3:   [//continuation of narrative]
         * 
*/ public Builder addCodewordWithCountry(String codeword, String country, String narrative) { StructuredNarrative structured = new StructuredNarrative().setCodeword(codeword).setCountry(country); String countrySlash = country != null ? country + "//" : ""; String prefix = "/" + StringUtils.trim(codeword) + countrySlash; for (String fragment : wrap(prefix, narrative)) { structured.addNarrativeFragment(fragment); } this.structured.add(structured); return this; } /** * Adds structured narrative content composed by codeword, wrapped narrative and supplementary narrative. * This line formats are used for fields with query and response numbers. * Will be serialized as: *
         * Line 1:      /number/[narrative][/supplement]
         * Lines 2-6    [//continuation of supplement]
         * or
         * Line 1:      /number/[narrative][/supplement]
         * Lines 2-6    [//continuation of narrative][/supplement]
         * 
* * @param number the API accepts a String however this line structure is normally used with query/answer numbers as codewords * @param narrative the primary query/answer text * @param supplement additional query/answer text, that will be added with slash separator */ public Builder addCodewordWithSupplement(String number, String narrative, String supplement) { StructuredNarrative structured = new StructuredNarrative(); String codeword = number != null ? number : ""; structured.setCodeword(codeword); String prefix = "/" + codeword + "/"; String text = supplement != null ? narrative + "/" + supplement : narrative; boolean isSupplement = false; for (String fragment : wrap(prefix, text)) { // check if fragment has supplement if (!isSupplement && fragment.indexOf('/') >= 0) { // contains supplement String primatyText = StringUtils.substringBefore(fragment, "/"); String supplementText = StringUtils.substringAfter(fragment, "/"); structured.addNarrativeFragment(primatyText); structured.addNarrativeSupplementFragment(supplementText); // following fragments will be part of supplement isSupplement = true; } else { if (isSupplement) { structured.addNarrativeSupplementFragment(fragment); } else { structured.addNarrativeFragment(fragment); } } } this.structured.add(structured); return this; } /** * Adds unstructured narrative content wrapped into lines without any format or slash separator */ public Builder addUnstructured(String narrative) { List lines = LineWrapper.wrapIntoList(narrative, lineLength); this.unstructuredFragments.addAll(lines); return this; } /** * Adds unstructured narrative content strictly wrapped into lines without any format or slash separator */ public Builder addUnstructuredStrict(String narrative) { List lines = LineWrapper.wrapIntoListStrict(narrative, lineLength); this.unstructuredFragments.addAll(lines); return this; } /** * Wraps the text into lines considering the prefix size for the first line and considering the "//" prefix * that would be added to the following lines in the serialization */ private List wrap(String prefix, String narrative) { if (narrative == null) { return Collections.emptyList(); } List fragments = new ArrayList<>(); // first line wrap should discount the prefix size List lines = LineWrapper.wrapIntoList(narrative, lineLength - prefix.length()); if (!lines.isEmpty()) { String firstLine = StringUtils.trimToNull(lines.get(0)); if (firstLine != null) { fragments.add(firstLine); } } if (!fragments.isEmpty()) { String remainder = StringUtils.trimToNull(StringUtils.substringAfter(narrative, fragments.get(0))); if (remainder != null) { // following lines wrap should discount the "//" that would be added in the serialization lines = LineWrapper.wrapIntoList(remainder, lineLength - 2); fragments.addAll(lines); } } return fragments; } public Narrative build() { return new Narrative(this); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy