Repository: xlate/staedi (java)

Issue:
EDIFACT release element throws exception from writer with UNA
EDIFACT release element throws exception from writer when using the `UNA` segment.

```
java.lang.StringIndexOutOfBoundsException: String index out of range: 20
	at java.lang.AbstractStringBuilder.charAt(AbstractStringBuilder.java:237)
	at java.lang.StringBuilder.charAt(StringBuilder.java:76)
	at io.xlate.edi.internal.stream.tokenization.EDIFACTDialect.parseVersion(EDIFACTDialect.java:97)
	at io.xlate.edi.internal.stream.tokenization.EDIFACTDialect.initialize(EDIFACTDialect.java:66)
	at io.xlate.edi.internal.stream.tokenization.EDIFACTDialect.processServiceStringAdvice(EDIFACTDialect.java:224)
	at io.xlate.edi.internal.stream.tokenization.EDIFACTDialect.appendHeader(EDIFACTDialect.java:172)
	at io.xlate.edi.internal.stream.StaEDIStreamWriter.write(StaEDIStreamWriter.java:314)
...
```

Pull request description:
Delay EDIFACT version parse until end of composite element UNB01
Fixes #68 

Patch:
diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/EDIFACTDialect.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/EDIFACTDialect.java
index 8681db7..207e942 100644
--- a/src/main/java/io/xlate/edi/internal/stream/tokenization/EDIFACTDialect.java
+++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/EDIFACTDialect.java
@@ -15,8 +15,8 @@
  ******************************************************************************/
 package io.xlate.edi.internal.stream.tokenization;
 
-import io.xlate.edi.stream.Location;
 import io.xlate.edi.stream.EDIStreamConstants.Standards;
+import io.xlate.edi.stream.Location;
 
 public class EDIFACTDialect extends Dialect {
 
@@ -73,10 +73,14 @@ public class EDIFACTDialect extends Dialect {
 
             if (releaseIndicator != ' ') {
                 characters.setClass(releaseIndicator, CharacterClass.RELEASE_CHARACTER);
+            } else {
+                releaseIndicator = '\0';
             }
 
             if (elementRepeater != ' ') {
                 characters.setClass(elementRepeater, CharacterClass.ELEMENT_REPEATER);
+            } else {
+                elementRepeater = '\0';
             }
 
             characters.setClass(segmentDelimiter, CharacterClass.SEGMENT_DELIMITER);
@@ -89,47 +93,19 @@ public class EDIFACTDialect extends Dialect {
     }
 
     private String[] parseVersion() {
-        int versionStart = findVersionStart(headerTag, header, elementDelimiter);
-
-        if (versionStart > -1) {
-            StringBuilder versionBuilder = new StringBuilder();
-
-            for (int i = versionStart; header.charAt(i) != elementDelimiter; i++) {
-                versionBuilder.append(header.charAt(i));
-            }
+        int versionStart = findVersionStart();
+        int versionEnd = header.indexOf(String.valueOf(elementDelimiter), versionStart);
 
-            return versionBuilder.toString().split('\\' + String.valueOf(componentDelimiter));
+        if (versionEnd - versionStart > 1) {
+            return header.substring(versionStart, versionEnd).split('\\' + String.valueOf(componentDelimiter));
         }
 
         return EMPTY;
     }
 
-    static int findVersionStart(String headerTag, StringBuilder header, char elementDelimiter) {
-        final int length = header.length();
-        int versionStart = -1;
-
-        if (UNB.equals(headerTag)) {
-            if (length >= 10) {
-                versionStart = 4;
-            }
-        } else if (length >= 18) {
-            for (int i = 11; i < length; i++) {
-                if (unbTag(header, i - 2) &&
-                        length >= i + 7 &&
-                        header.charAt(i + 1) == elementDelimiter) {
-                    versionStart = i + 2;
-                    break;
-                }
-            }
-        }
-
-        return versionStart;
-    }
-
-    static boolean unbTag(StringBuilder buffer, int position) {
-        return (buffer.charAt(position) == 'U' &&
-                buffer.charAt(position + 1) == 'N' &&
-                buffer.charAt(position + 2) == 'B');
+    int findVersionStart() {
+        // Skip four characters: UNB<delim>
+        return UNB.equals(headerTag) ? 4 : unbStart + 4;
     }
 
     @Override
@@ -220,10 +196,12 @@ public class EDIFACTDialect extends Dialect {
         }
 
         if (index > EDIFACT_UNA_LENGTH) {
-            if (unbStart > -1 && (index - unbStart) > 9) {
-                rejected = !initialize(characters);
-                return isConfirmed();
-            } else if (header.charAt(index) == 'B') {
+            if (unbStart > -1 && (index - unbStart) > 3) {
+                if (value == elementDelimiter) {
+                    rejected = !initialize(characters);
+                    return isConfirmed();
+                }
+            } else if (value == 'B') {
                 CharSequence un = header.subSequence(index - 2, index);
 
                 if ("UN".contentEquals(un)) {


Test patch:
diff --git a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java
index 8bffe23..d84e354 100644
--- a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java
@@ -129,7 +129,6 @@ class StaEDIStreamReaderTest implements ConstantsTest {
         expected.put(Delimiters.SEGMENT, '\'');
         expected.put(Delimiters.DATA_ELEMENT, '+');
         expected.put(Delimiters.COMPONENT_ELEMENT, ':');
-        expected.put(Delimiters.REPETITION, ' ');
         expected.put(Delimiters.RELEASE, '?');
         expected.put(Delimiters.DECIMAL, ',');
 
diff --git a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java
index 7dc7b3e..fade85f 100644
--- a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java
@@ -1820,4 +1820,46 @@ class StaEDIStreamWriterTest {
         assertEquals(106, segment.length());
         assertEquals("\n", segment.substring(105));
     }
+
+    @Test
+    void testVersionReleaseEDIFACTHeader() throws EDIStreamException {
+        EDIOutputFactory factory = EDIOutputFactory.newFactory();
+        ByteArrayOutputStream stream = new ByteArrayOutputStream(4096);
+        EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
+
+        writer.startInterchange();
+        writer.writeStartSegment("UNA").writeEndSegment();
+        // UNB+UNOA:4:::2+005435656:1+006415160:1+060515:1434+00000000000778~
+        writer.writeStartSegment("UNB");
+
+        writer.writeStartElement();
+        writer.writeComponent("UNOA");
+        writer.writeComponent("4");
+        writer.writeEmptyComponent();
+        writer.writeEmptyComponent();
+        writer.writeComponent("2");
+        writer.endElement();
+
+        writer.writeStartElement();
+        writer.writeComponent("005435656");
+        writer.writeComponent("1");
+        writer.endElement();
+
+        writer.writeStartElement();
+        writer.writeComponent("006415160");
+        writer.writeComponent("1");
+        writer.endElement();
+
+        writer.writeStartElement();
+        writer.writeComponent("060515");
+        writer.writeComponent("1434");
+        writer.endElement();
+
+        writer.writeElement("00000000000778");
+        writer.writeEndSegment();
+
+        writer.flush();
+        assertEquals("UNA:+.?*'UNB+UNOA:4:::2+005435656:1+006415160:1+060515:1434+00000000000778'",
+                     new String(stream.toByteArray()));
+    }
 }
diff --git a/src/test/java/io/xlate/edi/internal/stream/tokenization/EDIFACTDialectTest.java b/src/test/java/io/xlate/edi/internal/stream/tokenization/EDIFACTDialectTest.java
index 29f09d9..06904d8 100644
--- a/src/test/java/io/xlate/edi/internal/stream/tokenization/EDIFACTDialectTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/tokenization/EDIFACTDialectTest.java
@@ -17,6 +17,7 @@ package io.xlate.edi.internal.stream.tokenization;
 
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.junit.jupiter.api.Test;
@@ -50,8 +51,8 @@ class EDIFACTDialectTest {
     @Test
     void testGetVersionA() throws EDIException {
         EDIFACTDialect edifact = (EDIFACTDialect) DialectFactory.getDialect("UNA".toCharArray(), 0, 3);
-        edifact.header = new StringBuilder("UNA:+.?*'          UNB+UNOA:1+005435656:1+006415160:1+060515:1434+00000000000778'");
         CharacterSet characters = new CharacterSet();
+        "UNA:+. *'UNB+UNOA:1+111111111:1+222222222:1+200726:1455+1'".chars().forEach(c -> edifact.appendHeader(characters, (char) c));
         edifact.initialize(characters);
         assertArrayEquals(new String[] { "UNOA", "1" }, edifact.getVersion(), "Invalid version");
     }
@@ -65,4 +66,54 @@ class EDIFACTDialectTest {
         assertArrayEquals(new String[] { "UNOA", "1" }, edifact.getVersion(), "Invalid version");
     }
 
+    @Test
+    void testBlankReleaseChar() throws EDIException {
+        EDIFACTDialect edifact = (EDIFACTDialect) DialectFactory.getDialect("UNA".toCharArray(), 0, 3);
+        CharacterSet characters = new CharacterSet();
+        "UNA:+. *'UNB+UNOA:1+111111111:1+222222222:1+200726:1455+1'".chars().forEach(c -> edifact.appendHeader(characters, (char) c));
+
+        assertTrue(edifact.initialize(characters));
+        assertEquals('\'', edifact.getSegmentTerminator());
+        assertEquals('+', edifact.getDataElementSeparator());
+        assertEquals(':', edifact.getComponentElementSeparator());
+        assertEquals('.', edifact.getDecimalMark());
+        assertEquals('*', edifact.getRepetitionSeparator());
+        assertEquals('\0', edifact.getReleaseIndicator());
+    }
+
+    @Test
+    void testBlankVersionUNA() throws EDIException {
+        EDIFACTDialect edifact = (EDIFACTDialect) DialectFactory.getDialect("UNA".toCharArray(), 0, 3);
+        CharacterSet characters = new CharacterSet();
+        "UNA:+.?*'UNB+".chars().forEach(c -> assertTrue(edifact.appendHeader(characters, (char) c)));
+        assertFalse(edifact.appendHeader(characters, '+'));
+    }
+
+    @Test
+    void testBlankVersionUNB() throws EDIException {
+        EDIFACTDialect edifact = (EDIFACTDialect) DialectFactory.getDialect("UNB".toCharArray(), 0, 3);
+        CharacterSet characters = new CharacterSet();
+        "UNB++111111111:1+222222222:1+200726:1455+1".chars().forEach(c -> assertTrue(edifact.appendHeader(characters, (char) c)));
+        assertFalse(edifact.appendHeader(characters, '\''));
+    }
+
+    @Test
+    void testInvalidState_HeaderUBN() throws EDIException {
+        EDIFACTDialect edifact = (EDIFACTDialect) DialectFactory.getDialect("UNA".toCharArray(), 0, 3);
+        CharacterSet characters = new CharacterSet();
+        "UNA:+.?*'".chars().forEach(c -> edifact.appendHeader(characters, (char) c));
+        assertTrue(edifact.appendHeader(characters, 'U'));
+        assertFalse(edifact.appendHeader(characters, 'B'));
+    }
+
+    @Test
+    void testInvalidState_HeaderUNC() throws EDIException {
+        EDIFACTDialect edifact = (EDIFACTDialect) DialectFactory.getDialect("UNA".toCharArray(), 0, 3);
+        CharacterSet characters = new CharacterSet();
+        "UNA:+.?*'".chars().forEach(c -> edifact.appendHeader(characters, (char) c));
+        assertTrue(edifact.appendHeader(characters, 'U'));
+        assertTrue(edifact.appendHeader(characters, 'N'));
+        assertTrue(edifact.appendHeader(characters, 'C'));
+        assertFalse(edifact.appendHeader(characters, '+'));
+    }
 }