Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(PW-1862) Added NarrativeFragment class for detailed line information in StructuredNarrative fragments #189

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Prowide Core - CHANGELOG

#### 9.4.16 - SNAPSHOT
* (PW-1862) Added NarrativeFragment class for detailed line information in StructuredNarrative fragments
* Fixed SwiftMessage getPDE(): return empty value instead of null when codeword exists and has no value
* Added isPercentage() helper method to field 37K

#### 9.4.15 - March 2024
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public SwiftBlock2 deserialize(
&& jsonObject.get(DIRECTION).getAsString().equals("O")) {
return getSwiftBlock2OutputObject(jsonObject);
} else {
// defult to INPUT
// default to INPUT
return getSwiftBlock2InputObject(jsonObject);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1219,7 +1219,7 @@ public String getPDE() {
if (this.block5 != null) {
Optional<Tag> t = this.block5.getTag(SwiftBlock5Field.PDE);
if (t.isPresent()) {
return t.get().getValue();
return t.get().getValue() != null ? t.get().getValue() : "";
}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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;

/**
* Simple POJO for a fragment in a StructuredNarrative.
*
* <p>This model contains the narrative text, the line index (1 based) and the line length.
*
* <p>It is used by the {@link StructuredNarrative} class to get additional information of each line in the narrative.
*
* @since 9.4.16
*/
public class NarrativeFragment {
private final String text;
private final int lineIndex;
private final int lineLength;

/**
* Creates a new fragment without line index or length.
*
* @param text narrative line text
*/
public NarrativeFragment(final String text) {
this(text, 0, 0);
}

/**
* Creates a new fragment.
*
* @param text narrative line text
* @param lineIndex complete narrative line index
* @param lineLength complete line length
*/
public NarrativeFragment(final String text, final int lineIndex, final int lineLength) {
this.text = text;
this.lineIndex = lineIndex;
this.lineLength = lineLength;
}

public String getText() {
return text;
}

/**
* This is the 1-based index of the line in the complete narrative. Thus, regardless of the codeword position and
* on the number of line continuations, this index reflects this particular fragment position in the original
* field value before parsing. And it can be used for example to know if the fragment was located in the first
* line of the field value.
*
* @return 1-based index of the line this fragment belongs to, in the complete field value
*/
public int getLineIndex() {
return lineIndex;
}

/**
* This is the length of the complete line in the original field value before parsing. Thus, this number could
* contain for example the length of the codeword and slash separators before the actual narrative fragment, or
* when it is not the first fragment for a given codeword this could contain the length of the double slash used
* as continuation indicator. All in all, this value will be at least the size of the fragment, and in most cases
* it will be more. The purpose of this value is to provide a hint of the original line length, which could be used
* when reassembling the complete narrative for a codeword.
*
* @return the length of the complete line in the original field value before parsing
*/
public int getLineLength() {
return lineLength;
}

public String toString() {

Check notice

Code scanning / CodeQL

Missing Override annotation Note

This method overrides
Object.toString
; it is advisable to add an Override annotation.
return text;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,11 @@ private static Narrative parseFormat(
StructuredNarrative structured = new StructuredNarrative();
boolean firstSupplementAdded = false;
List<String> valueLines = notEmptyLines(value);
int lineIndex = 0;
for (String valueLine : valueLines) {
lineIndex++;
final int lineLength = valueLine.length();

if (unstructuredSection) {
narrative.addUnstructuredFragment(valueLine);
continue;
Expand All @@ -123,9 +127,9 @@ private static Narrative parseFormat(
if (supportsSupplement) {
firstSupplementAdded = addNarrativeSupplement(firstSupplementAdded, valueLine, structured);
} else if (StringUtils.isNotEmpty(valueLine)) {
structured.addNarrativeFragment(valueLine);
structured.addNarrativeFragment(valueLine, lineIndex, lineLength);
}
} else structured.addNarrativeFragment(valueLine);
} else structured.addNarrativeFragment(valueLine, lineIndex, lineLength);
} else {
// new codeword
String codeword = StringUtils.substringBetween(valueLine, "/", "/");
Expand Down Expand Up @@ -172,21 +176,23 @@ private static Narrative parseFormat(
if (supportsCountry) {
if (!textWithoutBankCode.isEmpty()) {
structured.addNarrativeFragment(
textWithoutBankCode); // structured.addNarrativeFragment(null);
textWithoutBankCode,
lineIndex,
lineLength); // structured.addNarrativeFragment(null);
}
} else {
structured.addNarrativeFragment(textWithoutBankCode);
structured.addNarrativeFragment(textWithoutBankCode, lineIndex, lineLength);
}
}

narrative.add(structured);
} else if (!additionalNarrativesStartWithDoubleSlash && !structured.isEmpty()) {
structured.addNarrativeFragment(valueLine);
structured.addNarrativeFragment(valueLine, lineIndex, lineLength);
unstructuredSection = false;
}
}
} else if (!additionalNarrativesStartWithDoubleSlash && !structured.isEmpty()) {
structured.addNarrativeFragment(valueLine);
structured.addNarrativeFragment(valueLine, lineIndex, lineLength);
unstructuredSection = false;
}
if (unstructuredSection) narrative.addUnstructuredFragment(valueLine);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@
*/
public class StructuredNarrative {
private final List<String> narrativeFragments = new ArrayList<>();
private final transient List<NarrativeFragment> narrativeFragmentsDetail = new ArrayList<>();
private final List<String> narrativeSupplementFragments = new ArrayList<>();
private String codeword;
private String currency;
private BigDecimal amount;
private String country;

private String bankCode;

/**
Expand Down Expand Up @@ -136,10 +136,34 @@ public List<String> getNarrativeFragments() {
}

/**
* @see #getNarrativeFragments()
* Returns the list of text fragments in the narrative including the line index and line length,
* fragments are segments of the text that is wrapped in lines.
* If the narrative is a single string in a single line, the list will contain a single element.
* To get the narrative as a simple joined string use {@link #getNarrative(String)}
*
* @return the narrative fragments or an empty list if the narrative does not have any text
*/
public List<NarrativeFragment> getNarrativeFragmentsDetail() {
return narrativeFragmentsDetail;
}

/**
* @see #addNarrativeFragment(String, int, int)
*/
StructuredNarrative addNarrativeFragment(String narrativeFragment) {
return addNarrativeFragment(narrativeFragment, 0, 0);
}

/**
* Adds a fragment indicating the line index (1 based) and the line length.
*
* @param narrativeFragment text of the fragment
* @param lineIndex complete narrative line index
* @param lineLength complete line length
*/
StructuredNarrative addNarrativeFragment(String narrativeFragment, int lineIndex, int lineLength) {
this.narrativeFragments.add(narrativeFragment);
this.narrativeFragmentsDetail.add(new NarrativeFragment(narrativeFragment, lineIndex, lineLength));
return this;
}

Expand Down Expand Up @@ -183,7 +207,7 @@ public String getNarrative() {
public String getNarrative(String delimiter) {
if (!this.narrativeFragments.isEmpty()) {
String s = delimiter != null ? delimiter : "";
return String.join(s, this.narrativeFragments);
return String.join(s, this.getNarrativeFragments());
}
return null;
}
Expand Down Expand Up @@ -216,7 +240,7 @@ public String getNarrativeSupplement(String delimiter) {
}

/**
* @return true if non of the narrative fields are set
* @return true if none of the narrative fields are set
*/
public boolean isEmpty() {
return this.codeword == null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,7 @@ protected Tag[] tags(final String tagName) {
public String toJson() {
final Gson gson = new GsonBuilder()
.registerTypeAdapter(AbstractMT.class, new AbstractMTAdapter())
.registerTypeAdapter(SwiftBlock2.class, new SwiftBlock2Adapter())
.setPrettyPrinting()
.create();
return gson.toJson(this, AbstractMT.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,56 @@ public void testFormat2_12() {
n.getStructured("ACC").getNarrativeFragments().get(1));
}

/**
* valid input
*/
@Test
public void testFormat2_13() {
String v = "/BNF/RETN\n"
+ "//THE TRANSACTION IS REJECTED DUE T\n"
+ "//O INTERNAL POLICY\n"
+ "/MREF/XXX55444/220424\n"
+ "/TEXT/WE CONSIDER YR MT103 AS NULL\n"
+ "//AND VOID\n";
Narrative n = NarrativeResolver.parse(new Field72(v));

assertNull(n.getUnstructured());

assertEquals(3, n.getStructured().size()); // This count only the CodeWords

StructuredNarrative BNF = n.getStructured().get(0);
StructuredNarrative MREF = n.getStructured().get(1);
StructuredNarrative TEXT = n.getStructured().get(2);

assertEquals(3, BNF.getNarrativeFragmentsDetail().size());
assertEquals(1, MREF.getNarrativeFragmentsDetail().size());
assertEquals(2, TEXT.getNarrativeFragmentsDetail().size());

// "/BNF/RETN"
assertEquals(9, BNF.getNarrativeFragmentsDetail().get(0).getLineLength());
assertEquals(1, BNF.getNarrativeFragmentsDetail().get(0).getLineIndex());

// "//THE TRANSACTION IS REJECTED DUE T"
assertEquals(35, BNF.getNarrativeFragmentsDetail().get(1).getLineLength());
assertEquals(2, BNF.getNarrativeFragmentsDetail().get(1).getLineIndex());

// "//O INTERNAL POLICY"
assertEquals(19, BNF.getNarrativeFragmentsDetail().get(2).getLineLength());
assertEquals(3, BNF.getNarrativeFragmentsDetail().get(2).getLineIndex());

// "/MREF/XXX55444/220424"
assertEquals(21, MREF.getNarrativeFragmentsDetail().get(0).getLineLength());
assertEquals(4, MREF.getNarrativeFragmentsDetail().get(0).getLineIndex());

// "/TEXT/WE CONSIDER YR MT103 AS NULL"
assertEquals(34, TEXT.getNarrativeFragmentsDetail().get(0).getLineLength());
assertEquals(5, TEXT.getNarrativeFragmentsDetail().get(0).getLineIndex());

// "//AND VOID"
assertEquals(10, TEXT.getNarrativeFragmentsDetail().get(1).getLineLength());
assertEquals(6, TEXT.getNarrativeFragmentsDetail().get(1).getLineIndex());
}

/*
* FORMAT 3
* Line 1: /8c/[3!a13d][additional information] (Code)(Currency)(Amount)(Narrative)
Expand Down
Loading