Repository: xlate/staedi
Pull request #355 (2023-04-26 21:28:10)

Refactoring ahead of writer validation fixes
Preparation for #283 and #286


Patch:
diff --git a/src/main/java/io/xlate/edi/internal/schema/ElementType.java b/src/main/java/io/xlate/edi/internal/schema/ElementType.java
index 0e1a415..ddd10b1 100644
--- a/src/main/java/io/xlate/edi/internal/schema/ElementType.java
+++ b/src/main/java/io/xlate/edi/internal/schema/ElementType.java
@@ -20,6 +20,7 @@ import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.function.BiFunction;
 import java.util.function.Supplier;
 
@@ -42,27 +43,27 @@ class ElementType extends BasicType implements EDISimpleType {
     final List<Version> versions;
 
     static class Version extends VersionedProperty {
-        final Long minLength;
-        final Long maxLength;
-        final Map<String, String> values;
+        final Optional<Long> minLength;
+        final Optional<Long> maxLength;
+        final Optional<Map<String, String>> values;
 
         Version(String minVersion, String maxVersion, Long minLength, Long maxLength, Map<String, String> values) {
             super(minVersion, maxVersion);
-            this.minLength = minLength;
-            this.maxLength = maxLength;
-            this.values = values;
+            this.minLength = Optional.ofNullable(minLength);
+            this.maxLength = Optional.ofNullable(maxLength);
+            this.values = Optional.ofNullable(values);
         }
 
         public long getMinLength(ElementType defaultElement) {
-            return minLength != null ? minLength.longValue() : defaultElement.getMinLength();
+            return minLength.orElseGet(defaultElement::getMinLength);
         }
 
         public long getMaxLength(ElementType defaultElement) {
-            return maxLength != null ? maxLength.longValue() : defaultElement.getMaxLength();
+            return maxLength.orElseGet(defaultElement::getMaxLength);
         }
 
         public Map<String, String> getValues(ElementType defaultElement) {
-            return values != null ? values : defaultElement.getValues();
+            return values.orElseGet(defaultElement::getValues);
         }
     }
 
diff --git a/src/main/java/io/xlate/edi/internal/schema/Reference.java b/src/main/java/io/xlate/edi/internal/schema/Reference.java
index 78ceec9..e26cc95 100644
--- a/src/main/java/io/xlate/edi/internal/schema/Reference.java
+++ b/src/main/java/io/xlate/edi/internal/schema/Reference.java
@@ -18,6 +18,7 @@ package io.xlate.edi.internal.schema;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 import java.util.function.BiFunction;
 import java.util.function.Supplier;
 
@@ -40,21 +41,21 @@ class Reference implements EDIReference {
     private final String description;
 
     static class Version extends VersionedProperty {
-        final Integer minOccurs;
-        final Integer maxOccurs;
+        final Optional<Integer> minOccurs;
+        final Optional<Integer> maxOccurs;
 
         Version(String minVersion, String maxVersion, Integer minOccurs, Integer maxOccurs) {
             super(minVersion, maxVersion);
-            this.minOccurs = minOccurs;
-            this.maxOccurs = maxOccurs;
+            this.minOccurs = Optional.ofNullable(minOccurs);
+            this.maxOccurs = Optional.ofNullable(maxOccurs);
         }
 
         public int getMinOccurs(Reference defaultElement) {
-            return minOccurs != null ? minOccurs.intValue() : defaultElement.getMinOccurs();
+            return minOccurs.orElseGet(defaultElement::getMinOccurs);
         }
 
         public int getMaxOccurs(Reference defaultElement) {
-            return maxOccurs != null ? maxOccurs.intValue() : defaultElement.getMaxOccurs();
+            return maxOccurs.orElseGet(defaultElement::getMaxOccurs);
         }
     }
 
diff --git a/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamLocation.java b/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamLocation.java
index 5aa686b..198ab75 100644
--- a/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamLocation.java
+++ b/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamLocation.java
@@ -19,7 +19,9 @@ import io.xlate.edi.stream.Location;
 
 public class StaEDIStreamLocation extends LocationView implements Location {
 
-    private boolean repeated = false;
+    private boolean composite = false;
+    private boolean repeating = false;
+    private int repeatCount = -1;
 
     public StaEDIStreamLocation() {
         super();
@@ -32,7 +34,9 @@ public class StaEDIStreamLocation extends LocationView implements Location {
     @Override
     public StaEDIStreamLocation copy() {
         StaEDIStreamLocation copy = new StaEDIStreamLocation(this);
-        copy.repeated = this.repeated;
+        copy.composite = this.composite;
+        copy.repeating = this.repeating;
+        copy.repeatCount = this.repeatCount;
         return copy;
     }
 
@@ -68,57 +72,93 @@ public class StaEDIStreamLocation extends LocationView implements Location {
         this.columnNumber++;
     }
 
-    public void incrementSegmentPosition(String segmentTag) {
-        if (this.segmentPosition < 0) {
-            this.segmentPosition = 1;
-        } else {
-            this.segmentPosition++;
+    static int initOrIncrement(int position) {
+        if (position < 0) {
+            return 1;
         }
+        return position + 1;
+    }
 
+    public void incrementSegmentPosition(String segmentTag) {
+        this.segmentPosition = initOrIncrement(segmentPosition);
         this.segmentTag = segmentTag;
-
         clearSegmentLocations();
     }
 
     public void clearSegmentLocations() {
         this.elementPosition = -1;
         this.elementOccurrence = -1;
+        this.repeating = false;
+        this.repeatCount = -1;
         clearComponentPosition();
     }
 
     public void incrementElementPosition() {
-        if (this.elementPosition < 0) {
-            this.elementPosition = 1;
-        } else {
-            this.elementPosition++;
-        }
-
+        this.elementPosition = initOrIncrement(elementPosition);
         this.elementOccurrence = 1;
         clearComponentPosition();
     }
 
     public void incrementElementOccurrence() {
-        this.elementOccurrence++;
+        this.elementPosition = Math.max(elementPosition, 1);
+        this.elementOccurrence = initOrIncrement(elementOccurrence);
         clearComponentPosition();
     }
 
     public void incrementComponentPosition() {
-        if (this.componentPosition < 0) {
-            this.componentPosition = 1;
-        } else {
-            this.componentPosition++;
-        }
+        this.componentPosition = initOrIncrement(componentPosition);
     }
 
     public void clearComponentPosition() {
+        this.composite = false;
         this.componentPosition = -1;
     }
 
-    public void setRepeated(boolean repeated) {
-        this.repeated = repeated;
+    public void setComposite(boolean composite) {
+        this.composite = composite;
     }
 
-    public boolean isRepeated() {
-        return repeated;
+    public void setRepeating(boolean repeating) {
+        if (repeating) {
+            // Encountered a repeat delimiter
+            if (this.repeating) {
+                // Previous delimiter was repeat, increment
+                repeatCount++;
+            } else {
+                // First repeat delimiter for this element
+                repeatCount = 0;
+            }
+        } else if (this.repeating) {
+            // Previous delimiter was repeat, this one is not. The element just completed is a repeat
+            repeatCount++;
+        } else {
+            // Repeat does not apply
+            repeatCount = -1;
+        }
+
+        this.repeating = repeating;
+    }
+
+    public void incrementElement(boolean compositeBegin) {
+        if (composite) {
+            incrementComponentPosition();
+        } else if (elementPosition < 0 || repeatCount == -1) {
+            // First element of the segment or not a repeating element
+            incrementElementPosition();
+        } else if (repeating) {
+            if (compositeBegin) {
+                // Previous element delimiter was a repeater and the first component was encountered
+                incrementElementOccurrence();
+            } else if (repeatCount == 0) {
+                // First element of the repeating series is in a new element position
+                incrementElementPosition();
+            } else {
+                incrementElementOccurrence();
+            }
+        } else if (compositeBegin) {
+            incrementElementPosition();
+        } else {
+            incrementElementOccurrence();
+        }
     }
 }
diff --git a/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java b/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java
index a43c3a7..21459a7 100644
--- a/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java
+++ b/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java
@@ -394,7 +394,7 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
     }
 
     @Override
-    public EDIStreamWriter startInterchange() throws EDIStreamException {
+    public EDIStreamWriter startInterchange() {
         ensureLevel(LEVEL_INITIAL);
         ensureState(State.INITIAL);
         level = LEVEL_INTERCHANGE;
@@ -526,6 +526,7 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
         if (level > LEVEL_SEGMENT) {
             validateElement(this.elementBuffer::flip, this.elementBuffer);
         }
+        level = LEVEL_SEGMENT;
         validate(validator -> validator.validateSyntax(dialect, this, this, location, false));
 
         if (state == State.ELEMENT_DATA_BINARY) {
@@ -858,8 +859,13 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
     }
 
     @Override
-    public boolean elementData(char[] text, int start, int length) {
-        elementHolder.set(text, start, length);
+    public boolean elementData(CharSequence text, boolean fromStream) {
+        if (level > LEVEL_ELEMENT) {
+            location.incrementComponentPosition();
+        } else {
+            location.incrementElementPosition();
+        }
+
         dialect.elementData(elementHolder, location);
 
         validator().ifPresent(validator -> {
diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/ElementDataHandler.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/ElementDataHandler.java
index 65ab321..635e97e 100644
--- a/src/main/java/io/xlate/edi/internal/stream/tokenization/ElementDataHandler.java
+++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/ElementDataHandler.java
@@ -4,7 +4,7 @@ import java.io.InputStream;
 
 public interface ElementDataHandler {
 
-    boolean elementData(char[] text, int start, int length);
+    boolean elementData(CharSequence text, boolean fromStream);
 
     boolean binaryData(InputStream binary);
 
diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/EventHandler.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/EventHandler.java
index 423dc0a..06e5d29 100644
--- a/src/main/java/io/xlate/edi/internal/stream/tokenization/EventHandler.java
+++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/EventHandler.java
@@ -25,7 +25,7 @@ public interface EventHandler extends ElementDataHandler, ValidationEventHandler
 
     boolean segmentEnd();
 
-    boolean compositeBegin(boolean isNil);
+    boolean compositeBegin(boolean isNil, boolean derived);
 
     boolean compositeEnd(boolean isNil);
 
diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/Lexer.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/Lexer.java
index c13ef60..455c1cb 100644
--- a/src/main/java/io/xlate/edi/internal/stream/tokenization/Lexer.java
+++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/Lexer.java
@@ -18,6 +18,7 @@ package io.xlate.edi.internal.stream.tokenization;
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.UncheckedIOException;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.Charset;
@@ -25,8 +26,10 @@ import java.nio.charset.CharsetDecoder;
 import java.nio.charset.CoderResult;
 import java.util.ArrayDeque;
 import java.util.Deque;
+import java.util.function.IntSupplier;
 import java.util.logging.Logger;
 
+import io.xlate.edi.internal.stream.CharArraySequence;
 import io.xlate.edi.internal.stream.LocationView;
 import io.xlate.edi.internal.stream.StaEDIStreamLocation;
 import io.xlate.edi.stream.Location;
@@ -60,6 +63,7 @@ public class Lexer {
     private char[] readChar = new char[1];
     private CharBuffer readCharBuf = CharBuffer.wrap(readChar);
     private ByteBuffer readByteBuf = ByteBuffer.allocate(4);
+    private CharArraySequence elementHolder = new CharArraySequence();
 
     private final StaEDIStreamLocation location;
     private final CharacterSet characters;
@@ -104,41 +108,19 @@ public class Lexer {
 
         ssn = (notifyState, start, length) -> {
             String segmentTag = new String(buffer.array(), start, length);
-            location.incrementSegmentPosition(segmentTag);
             return handler.segmentBegin(segmentTag);
         };
 
-        sen = (notifyState, start, length) -> {
-            boolean eventsReady = handler.segmentEnd();
-            location.clearSegmentLocations();
-            return eventsReady;
-        };
-
-        csn = (notifyState, start, length) -> {
-            if (location.isRepeated()) {
-                location.incrementElementOccurrence();
-            } else {
-                location.incrementElementPosition();
-            }
-
-            return handler.compositeBegin(false);
-        };
-
-        cen = (notifyState, start, length) -> {
-            boolean eventsReady = handler.compositeEnd(false);
-            location.clearComponentPosition();
-            return eventsReady;
-        };
+        sen = (notifyState, start, length) -> handler.segmentEnd();
+        csn = (notifyState, start, length) -> handler.compositeBegin(false, false);
+        cen = (notifyState, start, length) -> handler.compositeEnd(false);
+        bn = (notifyState, start, length) -> handler.binaryData(binaryStream);
 
         en = (notifyState, start, length) -> {
-            updateLocation(notifyState, location);
-            return handler.elementData(buffer.array(), start, length);
+            elementHolder.set(buffer.array(), start, length);
+            return handler.elementData(elementHolder, true);
         };
 
-        bn = (notifyState, start, length) -> {
-            updateLocation(notifyState, location);
-            return handler.binaryData(binaryStream);
-        };
     }
 
     public Dialect getDialect() {
@@ -175,6 +157,14 @@ public class Lexer {
     }
 
     public void parse() throws IOException, EDIException {
+        try {
+            parse(this::readCharacterUnchecked);
+        } catch (UncheckedIOException e) {
+            throw e.getCause();
+        }
+    }
+
+    void parse(IntSupplier inputSource) throws EDIException {
         if (nextEvent()) {
             return;
         }
@@ -186,7 +176,7 @@ public class Lexer {
 
         boolean eventsReady = false;
 
-        while (!eventsReady && (input = readCharacter()) > -1) {
+        while (!eventsReady && (input = inputSource.getAsInt()) > -1) {
             location.incrementOffset(input);
 
             CharacterClass clazz = characters.getClass(input);
@@ -305,6 +295,14 @@ public class Lexer {
         }
     }
 
+    int readCharacterUnchecked() {
+        try {
+            return readCharacter();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
     int readCharacter() throws IOException {
         int next = stream.read();
 
@@ -455,49 +453,6 @@ public class Lexer {
         return new EDIException(code, where);
     }
 
-    private static void updateLocation(State state, StaEDIStreamLocation location) {
-        if (state == State.ELEMENT_REPEAT) {
-            if (location.isRepeated()) {
-                updateElementOccurrence(location);
-            } else {
-                location.setElementOccurrence(1);
-            }
-            location.setRepeated(true);
-        } else if (location.isRepeated()) {
-            if (state != State.COMPONENT_END) {
-                updateElementOccurrence(location);
-                location.setRepeated(false);
-            }
-        } else {
-            location.setElementOccurrence(1);
-        }
-
-        switch (state) {
-        case COMPONENT_END:
-        case HEADER_COMPONENT_END:
-            location.incrementComponentPosition();
-            break;
-
-        default:
-            if (location.getComponentPosition() > 0) {
-                location.incrementComponentPosition();
-            } else if (location.getElementOccurrence() == 1) {
-                location.incrementElementPosition();
-            }
-            break;
-        }
-    }
-
-    static void updateElementOccurrence(StaEDIStreamLocation location) {
-        /*
-         * Only increment the position if we have not yet started
-         * the composite - i.e, only a single component is present.
-         */
-        if (location.getComponentPosition() < 1) {
-            location.incrementElementOccurrence();
-        }
-    }
-
     private boolean nextEvent() {
         Notifier event = events.peek();
         boolean eventsReady = false;
@@ -571,6 +526,8 @@ public class Lexer {
     }
 
     private void handleElement() throws EDIException {
+        location.setRepeating(State.ELEMENT_REPEAT.equals(state));
+
         if (previous != State.ELEMENT_END_BINARY) {
             addElementEvent();
         }
diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/ProxyEventHandler.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/ProxyEventHandler.java
index 09f615a..5ce11cd 100644
--- a/src/main/java/io/xlate/edi/internal/stream/tokenization/ProxyEventHandler.java
+++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/ProxyEventHandler.java
@@ -25,7 +25,6 @@ import java.util.Objects;
 import java.util.Queue;
 import java.util.function.Function;
 
-import io.xlate.edi.internal.stream.CharArraySequence;
 import io.xlate.edi.internal.stream.StaEDIStreamLocation;
 import io.xlate.edi.internal.stream.validation.UsageError;
 import io.xlate.edi.internal.stream.validation.Validator;
@@ -57,7 +56,6 @@ public class ProxyEventHandler implements EventHandler {
 
     private InputStream binary;
     private String segmentTag;
-    private CharArraySequence elementHolder = new CharArraySequence();
 
     private final Queue<StreamEvent> eventPool = new LinkedList<>();
     // Use implementation to access as both Deque & List
@@ -248,6 +246,7 @@ public class ProxyEventHandler implements EventHandler {
 
     @Override
     public boolean segmentBegin(String segmentTag) {
+        location.incrementSegmentPosition(segmentTag);
         this.segmentTag = segmentTag;
 
         /*
@@ -318,7 +317,12 @@ public class ProxyEventHandler implements EventHandler {
     }
 
     @Override
-    public boolean compositeBegin(boolean isNil) {
+    public boolean compositeBegin(boolean isNil, boolean derived) {
+        if (!derived) {
+            location.incrementElement(true);
+        }
+        location.setComposite(true);
+
         EDIReference typeReference = null;
         boolean eventsReady = true;
         Validator validator = validator();
@@ -357,18 +361,19 @@ public class ProxyEventHandler implements EventHandler {
     }
 
     @Override
-    public boolean elementData(char[] text, int start, int length) {
+    public boolean elementData(CharSequence text, boolean fromStream) {
+        location.incrementElement(false);
+
         boolean derivedComposite;
         EDIReference typeReference;
         final boolean compositeFromStream = location.getComponentPosition() > -1;
 
-        elementHolder.set(text, start, length);
-        dialect.elementData(elementHolder, location);
+        dialect.elementData(text, location);
         Validator validator = validator();
         boolean valid;
 
         if (levelCheckPending && startedLevel != null) {
-            setLevelIdentifiers();
+            setLevelIdentifiers(text);
         }
 
         /*
@@ -380,16 +385,16 @@ public class ProxyEventHandler implements EventHandler {
 
         if (validator != null) {
             derivedComposite = !compositeFromStream && validator.isComposite(dialect, location);
-            componentReceivedAsSimple = derivedComposite && text != null;
+            componentReceivedAsSimple = derivedComposite && fromStream;
 
             if (componentReceivedAsSimple) {
-                this.compositeBegin(elementHolder.length() == 0);
+                this.compositeBegin(text.length() == 0, true);
                 location.incrementComponentPosition();
             }
 
-            valid = validator.validateElement(dialect, location, elementHolder, null);
+            valid = validator.validateElement(dialect, location, text, null);
             typeReference = validator.getElementReference();
-            enqueueElementOccurrenceErrors(validator, valid);
+            enqueueElementOccurrenceErrors(text, validator, valid);
         } else {
             valid = true;
             derivedComposite = false;
@@ -397,14 +402,14 @@ public class ProxyEventHandler implements EventHandler {
             typeReference = null;
         }
 
-        enqueueElementErrors(validator, valid);
+        enqueueElementErrors(text, validator, valid);
 
         boolean eventsReady = true;
 
-        if (text != null && (!derivedComposite || length > 0) /* Not an inferred element */) {
+        if (fromStream && (!derivedComposite || text.length() > 0) /* Not an inferred element */) {
             enqueueEvent(EDIStreamEvent.ELEMENT_DATA,
                          EDIStreamValidationError.NONE,
-                         elementHolder,
+                         text,
                          typeReference,
                          location);
 
@@ -412,7 +417,7 @@ public class ProxyEventHandler implements EventHandler {
         }
 
         if (componentReceivedAsSimple) {
-            this.compositeEnd(length == 0);
+            this.compositeEnd(text.length() == 0);
             location.clearComponentPosition();
         }
 
@@ -446,13 +451,13 @@ public class ProxyEventHandler implements EventHandler {
                 loop.getParentIdPosition() != null;
     }
 
-    void setLevelIdentifiers() {
+    void setLevelIdentifiers(CharSequence text) {
         if (levelIdPosition.matchesLocation(location)) {
-            startedLevelId = elementHolder.toString();
+            startedLevelId = text.toString();
         }
 
         if (parentIdPosition.matchesLocation(location)) {
-            startedLevelParentId = elementHolder.toString();
+            startedLevelParentId = text.toString();
         }
     }
 
@@ -490,7 +495,7 @@ public class ProxyEventHandler implements EventHandler {
         }
     }
 
-    void enqueueElementOccurrenceErrors(Validator validator, boolean valid) {
+    void enqueueElementOccurrenceErrors(CharSequence text, Validator validator, boolean valid) {
         if (valid) {
             return;
         }
@@ -510,7 +515,7 @@ public class ProxyEventHandler implements EventHandler {
             case TOO_MANY_REPETITIONS:
                 enqueueEvent(error.getError().getCategory(),
                              error.getError(),
-                             elementHolder,
+                             text,
                              error.getTypeReference(),
                              location);
                 cursor.remove();
@@ -521,7 +526,7 @@ public class ProxyEventHandler implements EventHandler {
         }
     }
 
-    void enqueueElementErrors(Validator validator, boolean valid) {
+    void enqueueElementErrors(CharSequence text, Validator validator, boolean valid) {
         if (valid) {
             return;
         }
@@ -531,7 +536,7 @@ public class ProxyEventHandler implements EventHandler {
         for (UsageError error : errors) {
             enqueueEvent(error.getError().getCategory(),
                          error.getError(),
-                         elementHolder,
+                         text,
                          error.getTypeReference(),
                          location);
         }
@@ -543,6 +548,7 @@ public class ProxyEventHandler implements EventHandler {
 
     @Override
     public boolean binaryData(InputStream binaryStream) {
+        location.incrementElement(false);
         Validator validator = validator();
         EDIReference typeReference = validator != null ? validator.getElementReference() : null;
         enqueueEvent(EDIStreamEvent.ELEMENT_DATA_BINARY, EDIStreamValidationError.NONE, "", typeReference, location);
diff --git a/src/main/java/io/xlate/edi/internal/stream/validation/Validator.java b/src/main/java/io/xlate/edi/internal/stream/validation/Validator.java
index 44de8e8..e0ad7f8 100644
--- a/src/main/java/io/xlate/edi/internal/stream/validation/Validator.java
+++ b/src/main/java/io/xlate/edi/internal/stream/validation/Validator.java
@@ -1285,13 +1285,7 @@ public class Validator {
 
         // Ensure the start index is at least zero. Index may be -1 for empty segments
         for (int i = Math.max(index, 0), max = children.size(); i < max; i++) {
-            if (isComposite) {
-                location.incrementComponentPosition();
-            } else {
-                location.incrementElementPosition();
-            }
-
-            handler.elementData(null, 0, 0);
+            handler.elementData("", false);
         }
 
         if (!isComposite && implSegmentSelected && index == children.size()) {
diff --git a/src/main/java/io/xlate/edi/stream/EDIStreamReader.java b/src/main/java/io/xlate/edi/stream/EDIStreamReader.java
index 83bccde..433e00c 100644
--- a/src/main/java/io/xlate/edi/stream/EDIStreamReader.java
+++ b/src/main/java/io/xlate/edi/stream/EDIStreamReader.java
@@ -119,6 +119,29 @@ public interface EDIStreamReader extends Closeable, EDIStreamConstants {
      * Calls to this method are only valid when interchange type has been
      * determined, after START_INTERCHANGE.
      *
+     * The elements of the returned array are specific to the standard:
+     *
+     * <p>
+     * For EDIFACT:
+     * <ol>
+     * <li>Syntax identifier: UNB01-1
+     * <li>Syntax version number: UNB01-2
+     * <li>Service code list directory version number: UNB01-3 (syntax version 4+)
+     * <li>Character encoding, coded: UNB01-4 (syntax version 4+)
+     * <li>Syntax release number: UNB01-5 (syntax version and release 4.1+)
+     * </ol>
+     * <p>
+     * For X12:
+     * <ol>
+     * <li>Interchange Control Version Number: ISA12
+     * </ol>
+     * <p>
+     * For TRADACOMS:
+     * <ol>
+     * <li>Syntax rules identifier: STX01-1
+     * <li>Syntax rules version: STX01-2
+     * </ol>
+     *
      * @return the version
      * @throws IllegalStateException
      *             when the version has not yet been determined, prior to the
diff --git a/src/main/java/io/xlate/edi/stream/EDIStreamWriter.java b/src/main/java/io/xlate/edi/stream/EDIStreamWriter.java
index a9953cb..c92bbe8 100644
--- a/src/main/java/io/xlate/edi/stream/EDIStreamWriter.java
+++ b/src/main/java/io/xlate/edi/stream/EDIStreamWriter.java
@@ -143,6 +143,18 @@ public interface EDIStreamWriter extends AutoCloseable {
      */
     Map<String, Character> getDelimiters();
 
+    /**
+     * Initialize this writer to begin writing an interchange.
+     *
+     * This method does not write any output to the underlying stream.
+     *
+     * @return this EDI stream writer
+     * @throws EDIStreamException
+     *             is not thrown and will be removed in the next major version
+     *             of StAEDI.
+     * @throws IllegalStateException
+     *             when the writer is in any state other than the initial state
+     */
     EDIStreamWriter startInterchange() throws EDIStreamException;
 
     EDIStreamWriter endInterchange() throws EDIStreamException;


Test patch:
diff --git a/src/test/java/io/xlate/edi/internal/stream/tokenization/LexerTest.java b/src/test/java/io/xlate/edi/internal/stream/tokenization/LexerTest.java
index 331eb0b..fa9560b 100644
--- a/src/test/java/io/xlate/edi/internal/stream/tokenization/LexerTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/tokenization/LexerTest.java
@@ -80,7 +80,7 @@ class LexerTest {
         }
 
         @Override
-        public boolean compositeBegin(boolean isNil) {
+        public boolean compositeBegin(boolean isNil, boolean derived) {
             content.put("LAST", "compositeBegin");
             return true;
         }
@@ -92,9 +92,9 @@ class LexerTest {
         }
 
         @Override
-        public boolean elementData(char[] text, int start, int length) {
+        public boolean elementData(CharSequence text, boolean fromStream) {
             content.put("LAST", "elementData");
-            content.put("ELEMENT", new String(text, start, length));
+            content.put("ELEMENT", text.toString());
             return true;
         }
 
@@ -149,7 +149,7 @@ class LexerTest {
         }
 
         @Override
-        public boolean compositeBegin(boolean isNil) {
+        public boolean compositeBegin(boolean isNil, boolean derived) {
             compositeStarted = true;
             return true;
         }
@@ -160,9 +160,8 @@ class LexerTest {
             return true;
         }
 
-        @Override
-        public boolean elementData(char[] text, int start, int length) {
-            element = new String(text, start, length);
+        public boolean elementData(CharSequence text, boolean fromStream) {
+            element = text.toString();
             return true;
         }