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 4d59362a..b8d7b40b 100644 --- a/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java +++ b/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java @@ -30,6 +30,7 @@ import java.util.Map; import java.util.Objects; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; import java.util.logging.Logger; @@ -92,6 +93,7 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler, private CharBuffer elementBuffer = CharBuffer.allocate(500); private final StringBuilder formattedElement = new StringBuilder(); private List errors = new ArrayList<>(); + private CharArraySequence elementHolder = new CharArraySequence(); private char segmentTerminator; private char segmentTagTerminator; @@ -403,12 +405,6 @@ public EDIStreamWriter endInterchange() throws EDIStreamException { public EDIStreamWriter writeStartSegment(String name) throws EDIStreamException { ensureLevel(LEVEL_INTERCHANGE); location.incrementSegmentPosition(name); - validate(validator -> validator.validateSegment(this, name)); - - if (exitTransaction(name)) { - transaction = false; - validate(validator -> validator.validateSegment(this, name)); - } if (state == State.INITIAL) { dialect = DialectFactory.getDialect(name); @@ -422,19 +418,24 @@ public EDIStreamWriter writeStartSegment(String name) throws EDIStreamException */ dialect = DialectFactory.getDialect(EDIFACTDialect.UNA); writeServiceAdviceString(); + segmentValidation(name); // Now write the UNB writeString(name); } else { - writeString(name); - if (EDIFACTDialect.UNA.equals(name)) { + writeString(name); writeServiceAdviceCharacters(); + } else { + segmentValidation(name); + writeString(name); } } } else { + segmentValidation(name); writeString(name); } } else { + segmentValidation(name); writeString(name); } @@ -445,6 +446,15 @@ public EDIStreamWriter writeStartSegment(String name) throws EDIStreamException return this; } + void segmentValidation(String name) { + validate(validator -> validator.validateSegment(this, name)); + + if (exitTransaction(name)) { + transaction = false; + validate(validator -> validator.validateSegment(this, name)); + } + } + void terminateSegmentTag() throws EDIStreamException { if (this.segmentTagTerminator != '\0') { write(this.segmentTagTerminator); @@ -820,7 +830,15 @@ public boolean binaryData(InputStream binary) { @Override public boolean elementData(char[] text, int start, int length) { - // No operation + elementHolder.set(text, start, length); + dialect.elementData(elementHolder, location); + + withValidator(validator -> { + if (!validator.validateElement(dialect, location, elementHolder, null)) { + reportElementErrors(validator, elementHolder); + } + }); + return true; } @@ -880,82 +898,91 @@ public void segmentError(CharSequence token, EDIReference typeReference, EDIStre } private void validate(Consumer command) { - Validator validator = validator(); - - if (validator != null) { + withValidator(validator -> { errors.clear(); command.accept(validator); if (!errors.isEmpty()) { throw validationExceptionChain(errors); } - } + }); } private void validateCompositeOccurrence() { - final Validator validator = validator(); - - if (validator != null) { + withValidator(validator -> { errors.clear(); if (!validator.validCompositeOccurrences(dialect, location)) { - for (UsageError error : validator.getElementErrors()) { - elementError(error.getError().getCategory(), - error.getError(), - error.getTypeReference(), - "", - location.getElementPosition(), - location.getComponentPosition(), - location.getElementOccurrence()); - } + reportElementErrors(validator, ""); } if (!errors.isEmpty()) { throw validationExceptionChain(errors); } - } + }); } private CharSequence validateElement(Runnable setupCommand, CharSequence data) { - final Validator validator = validator(); - final CharSequence result; + return withValidator(validator -> { + CharSequence elementData; - if (validator != null) { if (this.formatElements) { - result = this.formattedElement; + elementData = this.formattedElement; this.formattedElement.setLength(0); this.formattedElement.append(data); // Validator will clear and re-format if configured } else { - result = data; + elementData = data; } errors.clear(); setupCommand.run(); if (!validator.validateElement(dialect, location, data, this.formattedElement)) { - for (UsageError error : validator.getElementErrors()) { - elementError(error.getError().getCategory(), - error.getError(), - error.getTypeReference(), - result, - location.getElementPosition(), - location.getComponentPosition(), - location.getElementOccurrence()); - } + reportElementErrors(validator, elementData); } if (!errors.isEmpty()) { throw validationExceptionChain(errors); } - dialect.elementData(result, location); + dialect.elementData(elementData, location); + return elementData; + }, () -> data); + } + + void withValidator(Consumer process) { + final Validator validator = validator(); + + if (validator != null) { + process.accept(validator); + } + } + + T withValidator(Function process, Supplier unvalidatedResult) { + final Validator validator = validator(); + final T result; + + if (validator != null) { + result = process.apply(validator); } else { - result = data; + result = unvalidatedResult.get(); } return result; } + void reportElementErrors(Validator validator, CharSequence data) { + for (UsageError error : validator.getElementErrors()) { + elementError(error.getError().getCategory(), + error.getError(), + error.getTypeReference(), + data, + location.getElementPosition(), + location.getComponentPosition(), + location.getElementOccurrence()); + } + } + EDIValidationException validationExceptionChain(List errors) { Iterator iter = errors.iterator(); EDIValidationException first = iter.next(); 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 bbd0f1e3..66541924 100644 --- a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java +++ b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java @@ -55,6 +55,7 @@ import io.xlate.edi.stream.EDIOutputFactory; import io.xlate.edi.stream.EDIStreamConstants; import io.xlate.edi.stream.EDIStreamConstants.Delimiters; +import io.xlate.edi.stream.EDIStreamConstants.Standards; import io.xlate.edi.stream.EDIStreamEvent; import io.xlate.edi.stream.EDIStreamException; import io.xlate.edi.stream.EDIStreamReader; @@ -2026,7 +2027,7 @@ void testOutputCompositeMissingSyntax() throws Exception { write(writer, "UNH", "1", new String[] { "CONTRL", "4", "2", "UN" }); write(writer, "UCI", "1", "SENDER", "RECEIVER", "7"); - writer.writeStartSegment("UCM").writeElement("1"); + writer.writeStartSegment("UCM").writeElement("1").writeEmptyElement().writeElement("7"); EDIValidationException thrown = assertThrows(EDIValidationException.class, () -> writer.writeEndSegment()); EDIStreamValidationError error = thrown.getError(); assertEquals(EDIStreamValidationError.CONDITIONAL_REQUIRED_DATA_ELEMENT_MISSING, error); @@ -2117,4 +2118,36 @@ void testIncompleteUNB() throws IOException, EDIStreamException, EDISchemaExcept assertEquals("UNA:+.? 'UNB+UNOA:3'UNH", new String(stream.toByteArray())); } + + @Test + void testIncompleteUNBValidated() throws IOException, EDIStreamException, EDISchemaException { + final EDIOutputFactory factory = EDIOutputFactory.newFactory(); + ByteArrayOutputStream stream = new ByteArrayOutputStream(4096); + EDIValidationException thrown = null; + + try (EDIStreamWriter writer = factory.createEDIStreamWriter(stream)) { + Schema schema = SchemaFactory.newFactory().getControlSchema(Standards.EDIFACT, new String[] { "UNOA", "3" }); + writer.setControlSchema(schema); + + writer.startInterchange(); + writer.writeStartSegment("UNA") + .writeEndSegment(); + writer.writeStartSegment("UNB") + .writeStartElement() + .writeComponent("UNOA") + .writeComponent("3") + .endElement(); + + thrown = assertThrows(EDIValidationException.class, () -> writer.writeEndSegment()); + } + + for (int position : Arrays.asList(2, 3, 4, 5)) { + assertEquals(EDIStreamEvent.ELEMENT_OCCURRENCE_ERROR, thrown.getEvent()); + assertEquals(EDIStreamValidationError.REQUIRED_DATA_ELEMENT_MISSING, thrown.getError()); + assertEquals("UNB", thrown.getLocation().getSegmentTag()); + assertEquals(2, thrown.getLocation().getSegmentPosition()); + assertEquals(position, thrown.getLocation().getElementPosition()); + thrown = thrown.getNextException(); + } + } }