Repository: xlate/staedi (java)

Issue:
Add padding to sender and receiver ids in ISA
As of now sender and receiver id in ISA segment is not padded with spaces to make their length to 15 chars.
These padding chars should be added automatically by API rather than the developer using the API to avoid exceptions


Pull request description:
Add option to format (pad) output elements to minimum required lengths
Fixes #108 

- Requires the use of a schema and at least 1 character written to the
element (to differentiate from an empty element)
- Remove unnecessary check for validator in `UsageNode`
- Remove validation functionality from element format methods

Patch:
diff --git a/src/main/java/io/xlate/edi/internal/stream/StaEDIOutputFactory.java b/src/main/java/io/xlate/edi/internal/stream/StaEDIOutputFactory.java
index 5f5fbee..1c3ccd5 100644
--- a/src/main/java/io/xlate/edi/internal/stream/StaEDIOutputFactory.java
+++ b/src/main/java/io/xlate/edi/internal/stream/StaEDIOutputFactory.java
@@ -40,6 +40,7 @@ public class StaEDIOutputFactory extends EDIOutputFactory {
         supportedProperties.add(EDIStreamConstants.Delimiters.RELEASE);
         supportedProperties.add(PRETTY_PRINT);
         supportedProperties.add(TRUNCATE_EMPTY_ELEMENTS);
+        supportedProperties.add(FORMAT_ELEMENTS);
 
         properties.put(PRETTY_PRINT, Boolean.FALSE);
     }
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 004a979..d5d0556 100644
--- a/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java
+++ b/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java
@@ -90,6 +90,7 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
     private CharArraySequence dataHolder = new CharArraySequence();
     private boolean atomicElementWrite = false;
     private CharBuffer elementBuffer = CharBuffer.allocate(500);
+    private final StringBuilder formattedElement = new StringBuilder();
     private List<EDIValidationException> errors = new ArrayList<>();
 
     private char segmentTerminator;
@@ -100,6 +101,7 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
     private char releaseIndicator;
 
     final boolean emptyElementTruncation;
+    final boolean formatElements;
     private final boolean prettyPrint;
     private String prettyPrintString;
 
@@ -118,6 +120,7 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
         this.reporter = reporter;
         this.emptyElementTruncation = booleanValue(properties.get(EDIOutputFactory.TRUNCATE_EMPTY_ELEMENTS));
         this.prettyPrint = booleanValue(properties.get(EDIOutputFactory.PRETTY_PRINT));
+        this.formatElements = booleanValue(properties.get(EDIOutputFactory.FORMAT_ELEMENTS));
         this.location = new StaEDIStreamLocation();
     }
 
@@ -244,17 +247,21 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
     public void setControlSchema(Schema controlSchema) {
         ensureLevel(LEVEL_INITIAL);
         this.controlSchema = controlSchema;
-        controlValidator = controlSchema != null ? new Validator(controlSchema, true, null) : null;
+        controlValidator = validatorInstance(controlSchema, null);
     }
 
     @Override
     public void setTransactionSchema(Schema transactionSchema) {
         if (!Objects.equals(this.transactionSchema, transactionSchema)) {
             this.transactionSchema = transactionSchema;
-            transactionValidator = transactionSchema != null ? new Validator(transactionSchema, true, controlSchema) : null;
+            transactionValidator = validatorInstance(transactionSchema, controlSchema);
         }
     }
 
+    Validator validatorInstance(Schema schema, Schema containerSchema) {
+        return schema != null ? new Validator(schema, containerSchema, true, formatElements) : null;
+    }
+
     @Override
     public Location getLocation() {
         return location;
@@ -597,8 +604,8 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
     public EDIStreamWriter writeElement(CharSequence text) throws EDIStreamException {
         atomicElementWrite = true;
         writeStartElement();
-        validateElement(() -> {}, text);
-        writeElementData(text);
+        CharSequence value = validateElement(() -> {}, text);
+        writeElementData(value);
         endElement();
         atomicElementWrite = false;
         return this;
@@ -608,8 +615,8 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
     public EDIStreamWriter writeElement(char[] text, int start, int end) throws EDIStreamException {
         atomicElementWrite = true;
         writeStartElement();
-        validateElement(() -> dataHolder.set(text, start, start + end), dataHolder);
-        writeElementData(text, start, end);
+        CharSequence value = validateElement(() -> dataHolder.set(text, start, start + end), dataHolder);
+        writeElementData(value);
         endElement();
         atomicElementWrite = false;
         return this;
@@ -619,6 +626,7 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
     public EDIStreamWriter writeEmptyElement() throws EDIStreamException {
         atomicElementWrite = true;
         writeStartElement();
+        // Ignore possibly-formatted value
         validateElement(dataHolder::clear, dataHolder);
         endElement();
         atomicElementWrite = false;
@@ -629,8 +637,8 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
     public EDIStreamWriter writeComponent(CharSequence text) throws EDIStreamException {
         atomicElementWrite = true;
         startComponent();
-        validateElement(() -> {}, text);
-        writeElementData(text);
+        CharSequence value = validateElement(() -> {}, text);
+        writeElementData(value);
         endComponent();
         atomicElementWrite = false;
         return this;
@@ -640,8 +648,8 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
     public EDIStreamWriter writeComponent(char[] text, int start, int end) throws EDIStreamException {
         atomicElementWrite = true;
         startComponent();
-        validateElement(() -> dataHolder.set(text, start, start + end), dataHolder);
-        writeElementData(text, start, end);
+        CharSequence value = validateElement(() -> dataHolder.set(text, start, start + end), dataHolder);
+        writeElementData(value);
         endComponent();
         atomicElementWrite = false;
         return this;
@@ -651,6 +659,7 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
     public EDIStreamWriter writeEmptyComponent() throws EDIStreamException {
         atomicElementWrite = true;
         startComponent();
+        // Ignore possibly-formatted value
         validateElement(dataHolder::clear, dataHolder);
         endComponent();
         atomicElementWrite = false;
@@ -864,19 +873,28 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
         }
     }
 
-    private void validateElement(Runnable setupCommand, CharSequence data) {
-        Validator validator = validator();
+    private CharSequence validateElement(Runnable setupCommand, CharSequence data) {
+        final Validator validator = validator();
+        final CharSequence result;
 
         if (validator != null) {
+            if (this.formatElements) {
+                result = this.formattedElement;
+                this.formattedElement.setLength(0);
+                this.formattedElement.append(data); // Validator will clear and re-format if configured
+            } else {
+                result = data;
+            }
+
             errors.clear();
             setupCommand.run();
 
-            if (!validator.validateElement(dialect, location, data)) {
+            if (!validator.validateElement(dialect, location, data, this.formattedElement)) {
                 for (UsageError error : validator.getElementErrors()) {
                     elementError(error.getError().getCategory(),
                                  error.getError(),
                                  error.getTypeReference(),
-                                 data,
+                                 result,
                                  location.getElementPosition(),
                                  location.getComponentPosition(),
                                  location.getElementOccurrence());
@@ -887,8 +905,12 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
                 throw validationExceptionChain(errors);
             }
 
-            dialect.elementData(data, location);
+            dialect.elementData(result, location);
+        } else {
+            result = data;
         }
+
+        return result;
     }
 
     EDIValidationException validationExceptionChain(List<EDIValidationException> errors) {
diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/EDIException.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/EDIException.java
index 205a76b..68a1be7 100644
--- a/src/main/java/io/xlate/edi/internal/stream/tokenization/EDIException.java
+++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/EDIException.java
@@ -62,8 +62,4 @@ public class EDIException extends EDIStreamException {
         super(exceptionMessages.get(id) + "; " + message);
     }
 
-    public EDIException(Throwable cause) {
-        super(cause);
-    }
-
 }
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 4b0cde1..7da0727 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
@@ -68,7 +68,7 @@ public class ProxyEventHandler implements EventHandler {
         }
 
         this.controlSchema = controlSchema;
-        controlValidator = controlSchema != null ? new Validator(controlSchema, validateCodeValues, null) : null;
+        controlValidator = validatorInstance(controlSchema, null, validateCodeValues);
     }
 
     public boolean isTransactionSchemaAllowed() {
@@ -82,10 +82,14 @@ public class ProxyEventHandler implements EventHandler {
     public void setTransactionSchema(Schema transactionSchema) {
         if (!Objects.equals(this.transactionSchema, transactionSchema)) {
             this.transactionSchema = transactionSchema;
-            transactionValidator = transactionSchema != null ? new Validator(transactionSchema, true, controlSchema) : null;
+            transactionValidator = validatorInstance(transactionSchema, controlSchema, true);
         }
     }
 
+    Validator validatorInstance(Schema schema, Schema containerSchema, boolean validateCodeValues) {
+        return schema != null ? new Validator(schema, containerSchema, validateCodeValues) : null;
+    }
+
     public void resetEvents() {
         eventCount = 0;
         eventIndex = 0;
@@ -246,7 +250,7 @@ public class ProxyEventHandler implements EventHandler {
 
         if (validator != null) {
             validator.validateSyntax(dialect, this, this, location, false);
-            validator.validateVersionConstraints(dialect, this);
+            validator.validateVersionConstraints(dialect, this, null);
             typeReference = validator.getSegmentReference();
         }
 
@@ -307,7 +311,7 @@ public class ProxyEventHandler implements EventHandler {
         boolean valid;
 
         if (validator != null) {
-            valid = validator.validateElement(dialect, location, elementHolder);
+            valid = validator.validateElement(dialect, location, elementHolder, null);
             derivedComposite = !compositeFromStream && validator.isComposite();
             typeReference = validator.getElementReference();
             enqueueElementOccurrenceErrors(validator, valid);
diff --git a/src/main/java/io/xlate/edi/internal/stream/validation/AlphaNumericValidator.java b/src/main/java/io/xlate/edi/internal/stream/validation/AlphaNumericValidator.java
index aa9dd3e..f94a8b8 100644
--- a/src/main/java/io/xlate/edi/internal/stream/validation/AlphaNumericValidator.java
+++ b/src/main/java/io/xlate/edi/internal/stream/validation/AlphaNumericValidator.java
@@ -15,17 +15,13 @@
  ******************************************************************************/
 package io.xlate.edi.internal.stream.validation;
 
-import java.io.IOException;
 import java.util.List;
 import java.util.Set;
 
 import io.xlate.edi.internal.stream.tokenization.CharacterSet;
 import io.xlate.edi.internal.stream.tokenization.Dialect;
-import io.xlate.edi.internal.stream.tokenization.EDIException;
 import io.xlate.edi.schema.EDISimpleType;
-import io.xlate.edi.stream.EDIStreamEvent;
 import io.xlate.edi.stream.EDIStreamValidationError;
-import io.xlate.edi.stream.EDIValidationException;
 
 class AlphaNumericValidator extends ElementValidator {
 
@@ -64,33 +60,15 @@ class AlphaNumericValidator extends ElementValidator {
     }
 
     @Override
-    void format(Dialect dialect, EDISimpleType element, CharSequence value, Appendable result) throws EDIException {
+    void format(Dialect dialect, EDISimpleType element, CharSequence value, StringBuilder result) {
         int length = value.length();
-        assertMaxLength(element, value);
 
-        Set<String> valueSet = element.getValueSet();
-
-        if (!valueSet.isEmpty() && !valueSet.contains(value.toString())) {
-            throw new EDIValidationException(EDIStreamEvent.ELEMENT_DATA, EDIStreamValidationError.INVALID_CODE_VALUE, null, value);
+        for (int i = 0; i < length; i++) {
+            result.append(value.charAt(i));
         }
 
-        try {
-            for (int i = 0; i < length; i++) {
-                char character = value.charAt(i);
-
-                if (!CharacterSet.isValid(character)) {
-                    throw new EDIException(EDIException.INVALID_CHARACTER,
-                                           "Invalid character 0x" + String.format("%04X", (int) character));
-                }
-
-                result.append(character);
-            }
-
-            for (long i = length, min = element.getMinLength(); i < min; i++) {
-                result.append(' ');
-            }
-        } catch (IOException e) {
-            throw new EDIException(e);
+        for (long i = length, min = element.getMinLength(); i < min; i++) {
+            result.append(' ');
         }
     }
 }
diff --git a/src/main/java/io/xlate/edi/internal/stream/validation/DateValidator.java b/src/main/java/io/xlate/edi/internal/stream/validation/DateValidator.java
index e7e0d6e..c22f8c2 100644
--- a/src/main/java/io/xlate/edi/internal/stream/validation/DateValidator.java
+++ b/src/main/java/io/xlate/edi/internal/stream/validation/DateValidator.java
@@ -15,16 +15,12 @@
  ******************************************************************************/
 package io.xlate.edi.internal.stream.validation;
 
-import java.io.IOException;
 import java.time.LocalDate;
 import java.util.List;
 
 import io.xlate.edi.internal.stream.tokenization.Dialect;
-import io.xlate.edi.internal.stream.tokenization.EDIException;
 import io.xlate.edi.schema.EDISimpleType;
-import io.xlate.edi.stream.EDIStreamEvent;
 import io.xlate.edi.stream.EDIStreamValidationError;
-import io.xlate.edi.stream.EDIValidationException;
 
 class DateValidator extends ElementValidator {
 
@@ -51,19 +47,8 @@ class DateValidator extends ElementValidator {
     }
 
     @Override
-    void format(Dialect dialect, EDISimpleType element, CharSequence value, Appendable result) throws EDIException {
-        assertMinLength(element, value);
-        assertMaxLength(element, value);
-
-        if (!validValue(value)) {
-            throw new EDIValidationException(EDIStreamEvent.ELEMENT_DATA, EDIStreamValidationError.INVALID_DATE, null, value);
-        }
-
-        try {
-            result.append(value);
-        } catch (IOException e) {
-            throw new EDIException(e);
-        }
+    void format(Dialect dialect, EDISimpleType element, CharSequence value, StringBuilder result) {
+        result.append(value);
     }
 
     static boolean validValue(CharSequence value) {
diff --git a/src/main/java/io/xlate/edi/internal/stream/validation/ElementValidator.java b/src/main/java/io/xlate/edi/internal/stream/validation/ElementValidator.java
index 4ff668d..e6b2535 100644
--- a/src/main/java/io/xlate/edi/internal/stream/validation/ElementValidator.java
+++ b/src/main/java/io/xlate/edi/internal/stream/validation/ElementValidator.java
@@ -18,11 +18,8 @@ package io.xlate.edi.internal.stream.validation;
 import java.util.List;
 
 import io.xlate.edi.internal.stream.tokenization.Dialect;
-import io.xlate.edi.internal.stream.tokenization.EDIException;
 import io.xlate.edi.schema.EDISimpleType;
-import io.xlate.edi.stream.EDIStreamEvent;
 import io.xlate.edi.stream.EDIStreamValidationError;
-import io.xlate.edi.stream.EDIValidationException;
 
 abstract class ElementValidator {
 
@@ -73,22 +70,6 @@ abstract class ElementValidator {
         return true;
     }
 
-    protected static void assertMinLength(EDISimpleType element, CharSequence value) {
-        if (value.length() < element.getMinLength()) {
-            throw new EDIValidationException(EDIStreamEvent.ELEMENT_DATA, EDIStreamValidationError.DATA_ELEMENT_TOO_SHORT, null, value);
-        }
-    }
-
-    protected static void assertMaxLength(EDISimpleType element, int length, CharSequence value) {
-        if (length > element.getMaxLength()) {
-            throw new EDIValidationException(EDIStreamEvent.ELEMENT_DATA, EDIStreamValidationError.DATA_ELEMENT_TOO_LONG, null, value);
-        }
-    }
-
-    protected static void assertMaxLength(EDISimpleType element, CharSequence value) {
-        assertMaxLength(element, value.length(), value);
-    }
-
     abstract void validate(Dialect dialect,
                            EDISimpleType element,
                            CharSequence value,
@@ -97,6 +78,5 @@ abstract class ElementValidator {
     abstract void format(Dialect dialect,
                          EDISimpleType element,
                          CharSequence value,
-                         Appendable result)
-            throws EDIException;
+                         StringBuilder result);
 }
diff --git a/src/main/java/io/xlate/edi/internal/stream/validation/NumericValidator.java b/src/main/java/io/xlate/edi/internal/stream/validation/NumericValidator.java
index 9d2be3c..a9eb318 100644
--- a/src/main/java/io/xlate/edi/internal/stream/validation/NumericValidator.java
+++ b/src/main/java/io/xlate/edi/internal/stream/validation/NumericValidator.java
@@ -15,15 +15,11 @@
  ******************************************************************************/
 package io.xlate.edi.internal.stream.validation;
 
-import java.io.IOException;
 import java.util.List;
 
 import io.xlate.edi.internal.stream.tokenization.Dialect;
-import io.xlate.edi.internal.stream.tokenization.EDIException;
 import io.xlate.edi.schema.EDISimpleType;
-import io.xlate.edi.stream.EDIStreamEvent;
 import io.xlate.edi.stream.EDIStreamValidationError;
-import io.xlate.edi.stream.EDIValidationException;
 
 class NumericValidator extends ElementValidator {
 
@@ -47,23 +43,14 @@ class NumericValidator extends ElementValidator {
     }
 
     @Override
-    void format(Dialect dialect, EDISimpleType element, CharSequence value, Appendable result) throws EDIException {
+    void format(Dialect dialect, EDISimpleType element, CharSequence value, StringBuilder result) {
         int length = validate(dialect, value);
-        assertMaxLength(element, Math.abs(length), value);
 
-        if (length < 0) {
-            throw new EDIValidationException(EDIStreamEvent.ELEMENT_DATA, EDIStreamValidationError.INVALID_CHARACTER_DATA, null, value);
+        for (long i = length, min = element.getMinLength(); i < min; i++) {
+            result.append('0');
         }
 
-        try {
-            for (long i = length, min = element.getMinLength(); i < min; i++) {
-                result.append('0');
-            }
-
-            result.append(value);
-        } catch (IOException e) {
-            throw new EDIException(e);
-        }
+        result.append(value);
     }
 
     /**
diff --git a/src/main/java/io/xlate/edi/internal/stream/validation/TimeValidator.java b/src/main/java/io/xlate/edi/internal/stream/validation/TimeValidator.java
index 9011cc5..3194ede 100644
--- a/src/main/java/io/xlate/edi/internal/stream/validation/TimeValidator.java
+++ b/src/main/java/io/xlate/edi/internal/stream/validation/TimeValidator.java
@@ -15,15 +15,11 @@
  ******************************************************************************/
 package io.xlate.edi.internal.stream.validation;
 
-import java.io.IOException;
 import java.util.List;
 
 import io.xlate.edi.internal.stream.tokenization.Dialect;
-import io.xlate.edi.internal.stream.tokenization.EDIException;
 import io.xlate.edi.schema.EDISimpleType;
-import io.xlate.edi.stream.EDIStreamEvent;
 import io.xlate.edi.stream.EDIStreamValidationError;
-import io.xlate.edi.stream.EDIValidationException;
 
 class TimeValidator extends ElementValidator {
 
@@ -49,22 +45,13 @@ class TimeValidator extends ElementValidator {
     }
 
     @Override
-    void format(Dialect dialect, EDISimpleType element, CharSequence value, Appendable result) throws EDIException {
+    void format(Dialect dialect, EDISimpleType element, CharSequence value, StringBuilder result) {
         int length = value.length();
-        assertMaxLength(element, value);
 
-        if (!validValue(value)) {
-            throw new EDIValidationException(EDIStreamEvent.ELEMENT_DATA, EDIStreamValidationError.INVALID_TIME, null, value);
-        }
-
-        try {
-            result.append(value);
+        result.append(value);
 
-            for (long i = length, min = element.getMinLength(); i < min; i++) {
-                result.append('0');
-            }
-        } catch (IOException e) {
-            throw new EDIException(e);
+        for (long i = length, min = element.getMinLength(); i < min; i++) {
+            result.append('0');
         }
     }
 
diff --git a/src/main/java/io/xlate/edi/internal/stream/validation/UsageNode.java b/src/main/java/io/xlate/edi/internal/stream/validation/UsageNode.java
index 600de4d..46a3eb7 100644
--- a/src/main/java/io/xlate/edi/internal/stream/validation/UsageNode.java
+++ b/src/main/java/io/xlate/edi/internal/stream/validation/UsageNode.java
@@ -141,13 +141,13 @@ class UsageNode {
     }
 
     void validate(Dialect dialect, CharSequence value, List<EDIStreamValidationError> errors) {
-        if (validator == null) {
-            throw new UnsupportedOperationException("simple type only");
-        }
-
         validator.validate(dialect, getSimpleType(), value, errors);
     }
 
+    void format(Dialect dialect, CharSequence value, StringBuilder result) {
+        validator.format(dialect, getSimpleType(), value, result);
+    }
+
     List<EDISyntaxRule> getSyntaxRules() {
         EDIType referencedNode = link.getReferencedType();
 
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 c204e24..c0c3639 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
@@ -77,6 +77,7 @@ public class Validator {
     private Schema containerSchema;
     private Schema schema;
     private final boolean validateCodeValues;
+    private final boolean formatElements;
     private boolean initial = true;
 
     final UsageNode root;
@@ -149,9 +150,14 @@ public class Validator {
         }
     }
 
-    public Validator(Schema schema, boolean validateCodeValues, Schema containerSchema) {
+    public Validator(Schema schema, Schema containerSchema, boolean validateCodeValues) {
+        this(schema, containerSchema, validateCodeValues, false);
+    }
+
+    public Validator(Schema schema, Schema containerSchema, boolean validateCodeValues, boolean formatElements) {
         this.schema = schema;
         this.validateCodeValues = validateCodeValues;
+        this.formatElements = formatElements;
         this.containerSchema = containerSchema;
 
         LOGGER.finer(() -> "Creating usage tree");
@@ -934,7 +940,7 @@ public class Validator {
         return composite != null && !StaEDISchema.ANY_COMPOSITE_ID.equals(composite.getId());
     }
 
-    public boolean validateElement(Dialect dialect, StaEDIStreamLocation position, CharSequence value) {
+    public boolean validateElement(Dialect dialect, StaEDIStreamLocation position, CharSequence value, StringBuilder formattedValue) {
         if (!segmentExpected) {
             return true;
         }
@@ -989,7 +995,7 @@ public class Validator {
         }
 
         if (valueReceived) {
-            validateElementValue(dialect, position, value);
+            validateElementValue(dialect, position, value, formattedValue);
         } else {
             validateDataElementRequirement(version);
         }
@@ -1026,7 +1032,7 @@ public class Validator {
         }
     }
 
-    void validateElementValue(Dialect dialect, StaEDIStreamLocation position, CharSequence value) {
+    void validateElementValue(Dialect dialect, StaEDIStreamLocation position, CharSequence value, StringBuilder formattedValue) {
         final String version = dialect.getTransactionVersionString();
 
         if (!element.isNodeType(EDIType.Type.COMPOSITE)) {
@@ -1047,12 +1053,12 @@ public class Validator {
             return;
         }
 
-        validateElementValue(dialect, this.element, this.implElement, value);
+        validateElementValue(dialect, this.element, this.implElement, value, formattedValue);
     }
 
-    public void validateVersionConstraints(Dialect dialect, ValidationEventHandler validationHandler) {
+    public void validateVersionConstraints(Dialect dialect, ValidationEventHandler validationHandler, StringBuilder formattedValue) {
         for (RevalidationNode entry : revalidationQueue) {
-            validateElementValue(dialect, entry.standard, entry.impl, entry.data);
+            validateElementValue(dialect, entry.standard, entry.impl, entry.data, formattedValue);
 
             for (UsageError error : elementErrors) {
                 validationHandler.elementError(error.getError().getCategory(),
@@ -1070,9 +1076,15 @@ public class Validator {
         revalidationQueue.clear();
     }
 
-    void validateElementValue(Dialect dialect, UsageNode element, UsageNode implElement, CharSequence value) {
+    void validateElementValue(Dialect dialect, UsageNode element, UsageNode implElement, CharSequence value, StringBuilder formattedValue) {
         List<EDIStreamValidationError> errors = new ArrayList<>();
-        element.validate(dialect, value, errors);
+        if (this.formatElements) {
+            formattedValue.setLength(0);
+            element.format(dialect, value, formattedValue);
+            value = formattedValue;
+        } else {
+            element.validate(dialect, value, errors);
+        }
 
         for (EDIStreamValidationError error : errors) {
             if (this.validateCodeValues || error != INVALID_CODE_VALUE) {
diff --git a/src/main/java/io/xlate/edi/stream/EDIOutputFactory.java b/src/main/java/io/xlate/edi/stream/EDIOutputFactory.java
index 08e33ef..b1b1635 100644
--- a/src/main/java/io/xlate/edi/stream/EDIOutputFactory.java
+++ b/src/main/java/io/xlate/edi/stream/EDIOutputFactory.java
@@ -42,6 +42,18 @@ public abstract class EDIOutputFactory extends PropertySupport {
      */
     public static final String TRUNCATE_EMPTY_ELEMENTS = "io.xlate.edi.stream.TRUNCATE_EMPTY_ELEMENTS";
 
+    /**
+     * <p>
+     * When set to true and a schema has been provided, elements written to the output will
+     * be padded to the minimum length required by the schema.
+     *
+     * <p>
+     * Default value is false.
+     *
+     * @since 1.11
+     */
+    public static final String FORMAT_ELEMENTS = "io.xlate.edi.stream.FORMAT_ELEMENTS";
+
     /**
      * Create a new instance of the factory. This static method creates a new
      * factory instance.
diff --git a/src/main/java/io/xlate/edi/stream/EDIStreamException.java b/src/main/java/io/xlate/edi/stream/EDIStreamException.java
index c18f64d..c1ae917 100644
--- a/src/main/java/io/xlate/edi/stream/EDIStreamException.java
+++ b/src/main/java/io/xlate/edi/stream/EDIStreamException.java
@@ -37,7 +37,10 @@ public class EDIStreamException extends Exception {
      *
      * @param cause
      *            a nested exception
+     * @deprecated
      */
+    @SuppressWarnings({ "java:S1133" })
+    @Deprecated/*(forRemoval = true, since = "1.11")*/
     public EDIStreamException(Throwable cause) {
         super(cause);
         location = null;


Test patch:
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 9dd24f1..0df52fd 100644
--- a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java
@@ -60,8 +60,10 @@ import io.xlate.edi.stream.Location;
 @SuppressWarnings("resource")
 class StaEDIStreamWriterTest {
 
+    private final String TEST_HEADER_X12 = "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *050812*1953*^*00501*508121953*0*P*:~";
+
     private void writeHeader(EDIStreamWriter writer) throws EDIStreamException {
-        //ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *050812*1953*^*00501*508121953*0*P*:~
+        // TEST_HEADER_X12
         writer.writeStartSegment("ISA");
         writer.writeElement("00").writeElement("          ");
         writer.writeElement("00").writeElement("          ");
@@ -1997,4 +1999,37 @@ class StaEDIStreamWriterTest {
         assertEquals("UNA:+.?*'UNB+UNOA:4:::2+005435656:1+006415160:1+060515:1434+00000000000778'",
                      new String(stream.toByteArray()));
     }
+
+    @Test
+    void testElementDataPadded() throws EDIStreamException, EDISchemaException {
+        EDIOutputFactory factory = EDIOutputFactory.newFactory();
+        factory.setProperty(EDIOutputFactory.FORMAT_ELEMENTS, true);
+
+        ByteArrayOutputStream stream = new ByteArrayOutputStream(4096);
+        EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
+
+        Schema control = SchemaFactory.newFactory().getControlSchema("X12", new String[] { "00501" });
+        writer.setControlSchema(control);
+        writer.startInterchange();
+
+        writer.writeStartSegment("ISA");
+        writer.writeElement("00").writeElement(" ");
+        writer.writeElement("00").writeElement(" ");
+        writer.writeElement("ZZ").writeElement("ReceiverID");
+        writer.writeElement("ZZ").writeElement("Sender");
+        writer.writeElement("050812");
+        writer.writeElement("1953");
+        writer.writeElement("^");
+        writer.writeElement("00501");
+        writer.writeElement("508121953");
+        writer.writeElement("0");
+        writer.writeElement("P");
+        writer.writeElement(":");
+        writer.writeEndSegment();
+        writer.flush();
+        String segment = new String(stream.toByteArray());
+
+        assertEquals(106, segment.length());
+        assertEquals(TEST_HEADER_X12, segment);
+    }
 }
diff --git a/src/test/java/io/xlate/edi/internal/stream/validation/AlphaNumericValidatorTest.java b/src/test/java/io/xlate/edi/internal/stream/validation/AlphaNumericValidatorTest.java
index c22c35d..147a89f 100644
--- a/src/test/java/io/xlate/edi/internal/stream/validation/AlphaNumericValidatorTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/validation/AlphaNumericValidatorTest.java
@@ -1,29 +1,22 @@
 package io.xlate.edi.internal.stream.validation;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.mockito.ArgumentMatchers;
 
 import io.xlate.edi.internal.stream.tokenization.CharacterSet;
 import io.xlate.edi.internal.stream.tokenization.Dialect;
 import io.xlate.edi.internal.stream.tokenization.DialectFactory;
 import io.xlate.edi.internal.stream.tokenization.EDIException;
 import io.xlate.edi.schema.EDISimpleType;
-import io.xlate.edi.stream.EDIStreamException;
 import io.xlate.edi.stream.EDIStreamValidationError;
-import io.xlate.edi.stream.EDIValidationException;
 
 class AlphaNumericValidatorTest implements ValueSetTester {
 
@@ -115,8 +108,8 @@ class AlphaNumericValidatorTest implements ValueSetTester {
         when(element.getMaxLength()).thenReturn(5L);
         ElementValidator v = AlphaNumericValidator.getInstance();
         StringBuilder output = new StringBuilder();
-        EDIValidationException e = assertThrows(EDIValidationException.class, () -> v.format(dialect, element, "TESTTEST", output));
-        assertEquals(EDIStreamValidationError.DATA_ELEMENT_TOO_LONG, e.getError());
+        v.format(dialect, element, "TESTTEST", output);
+        assertHasError(v, dialect, element, output, EDIStreamValidationError.DATA_ELEMENT_TOO_LONG);
     }
 
     @Test
@@ -131,8 +124,8 @@ class AlphaNumericValidatorTest implements ValueSetTester {
         when(element.getValueSet()).thenReturn(setOf("VAL1", "VAL2"));
         ElementValidator v = AlphaNumericValidator.getInstance();
         StringBuilder output = new StringBuilder();
-        EDIValidationException e = assertThrows(EDIValidationException.class, () -> v.format(dialect, element, "TESTTEST", output));
-        assertEquals(EDIStreamValidationError.INVALID_CODE_VALUE, e.getError());
+        v.format(dialect, element, "TESTTEST", output);
+        assertHasError(v, dialect, element, output, EDIStreamValidationError.INVALID_CODE_VALUE);
     }
 
     @Test
@@ -162,12 +155,8 @@ class AlphaNumericValidatorTest implements ValueSetTester {
         when(element.getMaxLength()).thenReturn(4L);
         ElementValidator v = AlphaNumericValidator.getInstance();
         StringBuilder output = new StringBuilder();
-        try {
-            v.format(dialect, element, "TES\u0008", output);
-            fail("Exception was expected");
-        } catch (EDIException e) {
-            assertTrue(e.getMessage().startsWith("EDIE004"));
-        }
+        v.format(dialect, element, "TES\u0008", output);
+        assertHasError(v, dialect, element, output, EDIStreamValidationError.INVALID_CHARACTER_DATA);
     }
 
     @Test
@@ -184,20 +173,4 @@ class AlphaNumericValidatorTest implements ValueSetTester {
         v.format(dialect, element, "TEST", output);
         assertEquals("TEST      ", output.toString());
     }
-
-    @Test
-    void testFormatValidValueIOException() throws Exception {
-        EDISimpleType element = mock(EDISimpleType.class);
-        when(element.getMinLength(anyString())).thenCallRealMethod();
-        when(element.getMaxLength(anyString())).thenCallRealMethod();
-        when(element.getValueSet(anyString())).thenCallRealMethod();
-
-        when(element.getMinLength()).thenReturn(10L);
-        when(element.getMaxLength()).thenReturn(10L);
-        ElementValidator v = AlphaNumericValidator.getInstance();
-        Appendable output = mock(Appendable.class);
-        when(output.append(ArgumentMatchers.anyChar())).thenThrow(IOException.class);
-        EDIStreamException thrown = assertThrows(EDIStreamException.class, () -> v.format(dialect, element, "TEST", output));
-        assertEquals(IOException.class, thrown.getCause().getClass());
-    }
 }
diff --git a/src/test/java/io/xlate/edi/internal/stream/validation/DateValidatorTest.java b/src/test/java/io/xlate/edi/internal/stream/validation/DateValidatorTest.java
index 70024db..ca0b543 100644
--- a/src/test/java/io/xlate/edi/internal/stream/validation/DateValidatorTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/validation/DateValidatorTest.java
@@ -1,7 +1,6 @@
 package io.xlate.edi.internal.stream.validation;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -19,7 +18,6 @@ import io.xlate.edi.internal.stream.tokenization.DialectFactory;
 import io.xlate.edi.internal.stream.tokenization.EDIException;
 import io.xlate.edi.schema.EDISimpleType;
 import io.xlate.edi.stream.EDIStreamValidationError;
-import io.xlate.edi.stream.EDIValidationException;
 
 class DateValidatorTest implements ValueSetTester {
 
@@ -201,8 +199,8 @@ class DateValidatorTest implements ValueSetTester {
         when(element.getMaxLength()).thenReturn(8L);
         ElementValidator v = DateValidator.getInstance();
         StringBuilder output = new StringBuilder();
-        EDIValidationException e = assertThrows(EDIValidationException.class, () -> v.format(dialect, element, "20000", output));
-        assertEquals(EDIStreamValidationError.DATA_ELEMENT_TOO_SHORT, e.getError());
+        v.format(dialect, element, "20000", output);
+        assertHasError(v, dialect, element, output, EDIStreamValidationError.DATA_ELEMENT_TOO_SHORT);
     }
 
     @Test
@@ -216,8 +214,8 @@ class DateValidatorTest implements ValueSetTester {
         when(element.getMaxLength()).thenReturn(8L);
         ElementValidator v = DateValidator.getInstance();
         StringBuilder output = new StringBuilder();
-        EDIValidationException e = assertThrows(EDIValidationException.class, () -> v.format(dialect, element, "200001011", output));
-        assertEquals(EDIStreamValidationError.DATA_ELEMENT_TOO_LONG, e.getError());
+        v.format(dialect, element, "200001011", output);
+        assertHasError(v, dialect, element, output, EDIStreamValidationError.DATA_ELEMENT_TOO_LONG);
     }
 
     @Test
@@ -231,8 +229,8 @@ class DateValidatorTest implements ValueSetTester {
         when(element.getMaxLength()).thenReturn(8L);
         ElementValidator v = DateValidator.getInstance();
         StringBuilder output = new StringBuilder();
-        EDIValidationException e = assertThrows(EDIValidationException.class, () -> v.format(dialect, element, "20000100", output));
-        assertEquals(EDIStreamValidationError.INVALID_DATE, e.getError());
+        v.format(dialect, element, "20000100", output);
+        assertHasError(v, dialect, element, output, EDIStreamValidationError.INVALID_DATE);
     }
 
     @Test
diff --git a/src/test/java/io/xlate/edi/internal/stream/validation/NumericValidatorTest.java b/src/test/java/io/xlate/edi/internal/stream/validation/NumericValidatorTest.java
index 361772b..ca9a14f 100644
--- a/src/test/java/io/xlate/edi/internal/stream/validation/NumericValidatorTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/validation/NumericValidatorTest.java
@@ -1,7 +1,6 @@
 package io.xlate.edi.internal.stream.validation;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -18,7 +17,6 @@ import io.xlate.edi.internal.stream.tokenization.DialectFactory;
 import io.xlate.edi.internal.stream.tokenization.EDIException;
 import io.xlate.edi.schema.EDISimpleType;
 import io.xlate.edi.stream.EDIStreamValidationError;
-import io.xlate.edi.stream.EDIValidationException;
 
 class NumericValidatorTest implements ValueSetTester {
 
@@ -107,8 +105,8 @@ class NumericValidatorTest implements ValueSetTester {
 
         ElementValidator v = NumericValidator.getInstance();
         StringBuilder output = new StringBuilder();
-        EDIValidationException e = assertThrows(EDIValidationException.class, () -> v.format(dialect, element, "123456", output));
-        assertEquals(EDIStreamValidationError.DATA_ELEMENT_TOO_LONG, e.getError());
+        v.format(dialect, element, "123456", output);
+        assertHasError(v, dialect, element, output, EDIStreamValidationError.DATA_ELEMENT_TOO_LONG);
     }
 
     @Test
@@ -122,8 +120,8 @@ class NumericValidatorTest implements ValueSetTester {
         when(element.getMaxLength()).thenReturn(5L);
         ElementValidator v = NumericValidator.getInstance();
         StringBuilder output = new StringBuilder();
-        EDIValidationException e = assertThrows(EDIValidationException.class, () -> v.format(dialect, element, "1234F", output));
-        assertEquals(EDIStreamValidationError.INVALID_CHARACTER_DATA, e.getError());
+        v.format(dialect, element, "1234F", output);
+        assertHasError(v, dialect, element, output, EDIStreamValidationError.INVALID_CHARACTER_DATA);
     }
 
     @Test
diff --git a/src/test/java/io/xlate/edi/internal/stream/validation/TimeValidatorTest.java b/src/test/java/io/xlate/edi/internal/stream/validation/TimeValidatorTest.java
index c45a7b8..d25e544 100644
--- a/src/test/java/io/xlate/edi/internal/stream/validation/TimeValidatorTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/validation/TimeValidatorTest.java
@@ -2,7 +2,6 @@ package io.xlate.edi.internal.stream.validation;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
@@ -20,7 +19,6 @@ import io.xlate.edi.internal.stream.tokenization.DialectFactory;
 import io.xlate.edi.internal.stream.tokenization.EDIException;
 import io.xlate.edi.schema.EDISimpleType;
 import io.xlate.edi.stream.EDIStreamValidationError;
-import io.xlate.edi.stream.EDIValidationException;
 
 class TimeValidatorTest implements ValueSetTester {
 
@@ -128,8 +126,8 @@ class TimeValidatorTest implements ValueSetTester {
         when(element.getValueSet()).thenReturn(setOf());
         ElementValidator v = TimeValidator.getInstance();
         StringBuilder output = new StringBuilder();
-        EDIValidationException e = assertThrows(EDIValidationException.class, () -> v.format(dialect, element, "1230599999", output));
-        assertEquals(EDIStreamValidationError.DATA_ELEMENT_TOO_LONG, e.getError());
+        v.format(dialect, element, "1230599999", output);
+        assertHasError(v, dialect, element, output, EDIStreamValidationError.DATA_ELEMENT_TOO_LONG);
     }
 
     @Test
@@ -144,8 +142,8 @@ class TimeValidatorTest implements ValueSetTester {
         when(element.getValueSet()).thenReturn(setOf());
         ElementValidator v = TimeValidator.getInstance();
         StringBuilder output = new StringBuilder();
-        EDIValidationException e = assertThrows(EDIValidationException.class, () -> v.format(dialect, element, "123059AA", output));
-        assertEquals(EDIStreamValidationError.INVALID_TIME, e.getError());
+        v.format(dialect, element, "123059AA", output);
+        assertHasError(v, dialect, element, output, EDIStreamValidationError.INVALID_TIME);
     }
 
     @Test
diff --git a/src/test/java/io/xlate/edi/internal/stream/validation/ValidatorTest.java b/src/test/java/io/xlate/edi/internal/stream/validation/ValidatorTest.java
index c95311f..489eef9 100644
--- a/src/test/java/io/xlate/edi/internal/stream/validation/ValidatorTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/validation/ValidatorTest.java
@@ -18,7 +18,7 @@ class ValidatorTest {
     void testValidatorRootAttributes() throws EDISchemaException {
         SchemaFactory schemaFactory = SchemaFactory.newFactory();
         Schema schema = schemaFactory.getControlSchema(Standards.X12, new String[] { "00801" });
-        Validator validator = new Validator(schema, true, null);
+        Validator validator = new Validator(schema, null, true);
         EDIReference interchangeReference = validator.root.getLink();
 
         assertSame(schema.getStandard(), interchangeReference.getReferencedType());
@@ -34,7 +34,7 @@ class ValidatorTest {
     void testValidatorRootNoParent() throws EDISchemaException {
         SchemaFactory schemaFactory = SchemaFactory.newFactory();
         Schema schema = schemaFactory.getControlSchema(Standards.X12, new String[] { "00801" });
-        Validator validator = new Validator(schema, true, null);
+        Validator validator = new Validator(schema, null, true);
         UsageNode root = validator.root;
 
         assertNull(root.getParent());
diff --git a/src/test/java/io/xlate/edi/internal/stream/validation/ValueSetTester.java b/src/test/java/io/xlate/edi/internal/stream/validation/ValueSetTester.java
index d462637..af003a8 100644
--- a/src/test/java/io/xlate/edi/internal/stream/validation/ValueSetTester.java
+++ b/src/test/java/io/xlate/edi/internal/stream/validation/ValueSetTester.java
@@ -1,10 +1,18 @@
 package io.xlate.edi.internal.stream.validation;
 
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
+import io.xlate.edi.internal.stream.tokenization.Dialect;
+import io.xlate.edi.schema.EDISimpleType;
+import io.xlate.edi.stream.EDIStreamValidationError;
+
 interface ValueSetTester {
 
     default Set<String> setOf() {
@@ -14,4 +22,10 @@ interface ValueSetTester {
     default Set<String> setOf(String... values) {
         return new HashSet<>(Arrays.asList(values));
     }
+
+    default void assertHasError(ElementValidator v, Dialect dialect, EDISimpleType element, CharSequence value, EDIStreamValidationError expected) {
+        List<EDIStreamValidationError> errors = new ArrayList<>();
+        v.validate(dialect, element, value, errors);
+        assertTrue(errors.contains(expected));
+    }
 }