diff --git a/pom.xml b/pom.xml
index 656773d1..71a04901 100644
--- a/pom.xml
+++ b/pom.xml
@@ -352,7 +352,7 @@
(,11)
- 2.0.1
+ 2.0.2
1.0.2
3.0.1
3.0.2
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 1c3ccd5a..13a6d454 100644
--- a/src/main/java/io/xlate/edi/internal/stream/StaEDIOutputFactory.java
+++ b/src/main/java/io/xlate/edi/internal/stream/StaEDIOutputFactory.java
@@ -41,6 +41,7 @@ public StaEDIOutputFactory() {
supportedProperties.add(PRETTY_PRINT);
supportedProperties.add(TRUNCATE_EMPTY_ELEMENTS);
supportedProperties.add(FORMAT_ELEMENTS);
+ supportedProperties.add(EDI_VALIDATE_CONTROL_STRUCTURE);
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 39de8e44..c279f6af 100644
--- a/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java
+++ b/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java
@@ -15,6 +15,7 @@
******************************************************************************/
package io.xlate.edi.internal.stream;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -28,27 +29,21 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.function.Consumer;
import java.util.function.Supplier;
+import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.stream.Collectors;
-import io.xlate.edi.internal.stream.tokenization.CharacterClass;
-import io.xlate.edi.internal.stream.tokenization.CharacterSet;
+import io.xlate.edi.internal.schema.SchemaUtils;
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.internal.stream.tokenization.EDIFACTDialect;
-import io.xlate.edi.internal.stream.tokenization.ElementDataHandler;
-import io.xlate.edi.internal.stream.tokenization.State;
-import io.xlate.edi.internal.stream.tokenization.ValidationEventHandler;
-import io.xlate.edi.internal.stream.tokenization.X12Dialect;
-import io.xlate.edi.internal.stream.validation.UsageError;
-import io.xlate.edi.internal.stream.validation.Validator;
-import io.xlate.edi.schema.EDIReference;
-import io.xlate.edi.schema.EDIType;
+import io.xlate.edi.internal.stream.tokenization.Lexer;
+import io.xlate.edi.internal.stream.tokenization.ProxyEventHandler;
+import io.xlate.edi.schema.EDISchemaException;
import io.xlate.edi.schema.Schema;
+import io.xlate.edi.stream.EDIInputFactory;
import io.xlate.edi.stream.EDIOutputErrorReporter;
import io.xlate.edi.stream.EDIOutputFactory;
import io.xlate.edi.stream.EDIStreamConstants.Delimiters;
@@ -59,7 +54,7 @@
import io.xlate.edi.stream.EDIValidationException;
import io.xlate.edi.stream.Location;
-public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler, ValidationEventHandler {
+public class StaEDIStreamWriter implements EDIStreamWriter, Configurable /*ElementDataHandler, ValidationEventHandler*/ {
static final Logger LOGGER = Logger.getLogger(StaEDIStreamWriter.class.getName());
@@ -71,30 +66,36 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
private static final int LEVEL_COMPONENT = 5;
private int level;
+ private boolean dialectHeaderReceived = false;
+ private boolean dialectHeaderProcessed = false;
+ private boolean inBinaryElement = false;
- private State state = State.INITIAL;
- private CharacterSet characters = new CharacterSet();
+ //private State state = State.INITIAL;
+ //private CharacterSet characters = new CharacterSet();
private final OutputStream stream;
private final OutputStreamWriter writer;
+ private final ProxyEventHandler proxy;
+ private final Lexer lexer;
private final Map properties;
private final EDIOutputErrorReporter reporter;
private Dialect dialect;
- CharBuffer unconfirmedBuffer = CharBuffer.allocate(500);
+ //CharBuffer unconfirmedBuffer = CharBuffer.allocate(500);
- private final StaEDIStreamLocation location;
+ private StaEDIStreamLocation location;
private Schema controlSchema;
- private Validator controlValidator;
- private boolean transactionSchemaAllowed = false;
- private boolean transaction = false;
- private Schema transactionSchema;
- private Validator transactionValidator;
- private CharArraySequence dataHolder = new CharArraySequence();
- private boolean atomicElementWrite = false;
- private CharBuffer elementBuffer = CharBuffer.allocate(500);
- private final StringBuilder formattedElement = new StringBuilder();
+ //private Validator controlValidator;
+ //private boolean transactionSchemaAllowed = false;
+ //private boolean transaction = false;
+ //private Schema transactionSchema;
+ //private Validator transactionValidator;
+ //private CharArraySequence dataHolder = new CharArraySequence();
+ //private boolean atomicElementWrite = false;
+ //private CharBuffer elementBuffer = CharBuffer.allocate(500);
+ //private final StringBuilder formattedElement = new StringBuilder();
private List errors = new ArrayList<>();
- private CharArraySequence elementHolder = new CharArraySequence();
+ //private CharArraySequence elementHolder = new CharArraySequence();
+ CharBuffer outputBuffer = (CharBuffer) CharBuffer.allocate(500).clear();
private char segmentTerminator;
private char segmentTagTerminator;
@@ -122,34 +123,24 @@ public StaEDIStreamWriter(OutputStream stream, Charset charset, Map(properties);
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.emptyElementTruncation = getProperty(EDIOutputFactory.TRUNCATE_EMPTY_ELEMENTS, Boolean::parseBoolean, false);
+ this.prettyPrint = getProperty(EDIOutputFactory.PRETTY_PRINT, Boolean::parseBoolean, false);
+ this.formatElements = getProperty(EDIOutputFactory.FORMAT_ELEMENTS, Boolean::parseBoolean, false);
this.location = new StaEDIStreamLocation();
- }
- boolean booleanValue(Object value) {
- if (value instanceof Boolean) {
- return (Boolean) value;
- }
- if (value instanceof String) {
- return Boolean.valueOf(value.toString());
- }
- if (value == null) {
- return false;
- }
- LOGGER.warning(() -> "Value [" + value + "] could not be converted to boolean");
- return false;
+ StaEDIStreamLocation outLocation = new StaEDIStreamLocation();
+ this.proxy = new ProxyEventHandler(outLocation, null, false);
+ this.lexer = new Lexer(new ByteArrayInputStream(new byte[0]), Charset.defaultCharset(), proxy, outLocation, false);
}
- private void setupDelimiters() {
- segmentTerminator = getDelimiter(properties, Delimiters.SEGMENT, dialect::getSegmentTerminator);
+ private void setupDelimiters(Dialect dialect) {
+ segmentTerminator = getDelimiter(dialect.isConfirmed(), properties, Delimiters.SEGMENT, dialect::getSegmentTerminator);
segmentTagTerminator = dialect.getSegmentTagTerminator(); // Not configurable - TRADACOMS
- dataElementSeparator = getDelimiter(properties, Delimiters.DATA_ELEMENT, dialect::getDataElementSeparator);
- componentElementSeparator = getDelimiter(properties, Delimiters.COMPONENT_ELEMENT, dialect::getComponentElementSeparator);
- decimalMark = getDelimiter(properties, Delimiters.DECIMAL, dialect::getDecimalMark);
- releaseIndicator = getDelimiter(properties, Delimiters.RELEASE, dialect::getReleaseIndicator);
- repetitionSeparator = getDelimiter(properties, Delimiters.REPETITION, dialect::getRepetitionSeparator);
+ dataElementSeparator = getDelimiter(dialect.isConfirmed(), properties, Delimiters.DATA_ELEMENT, dialect::getDataElementSeparator);
+ componentElementSeparator = getDelimiter(dialect.isConfirmed(), properties, Delimiters.COMPONENT_ELEMENT, dialect::getComponentElementSeparator);
+ decimalMark = getDelimiter(dialect.isConfirmed(), properties, Delimiters.DECIMAL, dialect::getDecimalMark);
+ releaseIndicator = getDelimiter(dialect.isConfirmed(), properties, Delimiters.RELEASE, dialect::getReleaseIndicator);
+ repetitionSeparator = getDelimiter(dialect.isConfirmed(), properties, Delimiters.REPETITION, dialect::getRepetitionSeparator);
String lineSeparator = System.getProperty("line.separator");
@@ -172,8 +163,8 @@ private boolean areDelimitersSpecified() {
.anyMatch(properties::containsKey);
}
- char getDelimiter(Map properties, String key, Supplier dialectSupplier) {
- if (properties.containsKey(key) && !dialect.isConfirmed()) {
+ char getDelimiter(boolean confirmed, Map properties, String key, Supplier dialectSupplier) {
+ if (properties.containsKey(key) && !confirmed) {
return (char) properties.get(key);
}
return dialectSupplier.get();
@@ -201,9 +192,9 @@ private void ensureFalse(boolean illegalState) {
}
}
- private void ensureState(State s) {
- ensureFalse(this.state != s);
- }
+// private void ensureState(State s) {
+// ensureFalse(this.state != s);
+// }
private void ensureLevel(int l) {
ensureFalse(this.level != l);
@@ -235,7 +226,6 @@ public void close() throws EDIStreamException {
public void flush() throws EDIStreamException {
try {
writer.flush();
- stream.flush();
} catch (IOException e) {
throw new EDIStreamException("Exception flushing output stream", location, e);
}
@@ -249,21 +239,23 @@ public Schema getControlSchema() {
@Override
public void setControlSchema(Schema controlSchema) {
ensureLevel(LEVEL_INITIAL);
+ proxy.setControlSchema(controlSchema, true);
this.controlSchema = controlSchema;
- controlValidator = Validator.forSchema(controlSchema, null, true, formatElements);
+// controlValidator = Validator.forSchema(controlSchema, null, true, formatElements);
}
@Override
public void setTransactionSchema(Schema transactionSchema) {
- if (!Objects.equals(this.transactionSchema, transactionSchema)) {
- this.transactionSchema = transactionSchema;
- transactionValidator = Validator.forSchema(transactionSchema, controlSchema, true, formatElements);
- }
+ proxy.setTransactionSchema(transactionSchema);
+// if (!Objects.equals(this.transactionSchema, transactionSchema)) {
+// this.transactionSchema = transactionSchema;
+// transactionValidator = Validator.forSchema(transactionSchema, controlSchema, true, formatElements);
+// }
}
@Override
public Location getLocation() {
- return location;
+ return location != null ? location : lexer.getLocation();
}
@Override
@@ -292,111 +284,122 @@ public Map getDelimiters() {
return delimiters;
}
- private Optional validator() {
- Validator validator;
-
- // Do not use the transactionValidator in the period where it may be set/mutated by the user
- if (transaction && !transactionSchemaAllowed) {
- validator = transactionValidator;
- } else {
- validator = controlValidator;
- }
-
- return Optional.ofNullable(validator);
- }
-
- private void write(int output) throws EDIStreamException {
- write(output, false);
- }
-
- private void write(int output, boolean isPrettyPrint) throws EDIStreamException {
- CharacterClass clazz;
-
- clazz = characters.getClass(output);
-
- if (clazz == CharacterClass.INVALID) {
- throw new EDIStreamException(String.format("Invalid character: 0x%04X", output), location);
- }
-
- state = State.transition(state, dialect, clazz);
-
- switch (state) {
- case HEADER_X12_I: // I(SA)
- case HEADER_EDIFACT_U: // U(NA) or U(NB)
- case HEADER_TRADACOMS_S: // S(TX)
- unconfirmedBuffer.clear();
- writeHeader((char) output, isPrettyPrint);
- break;
- case HEADER_X12_S:
- case HEADER_EDIFACT_N:
- case HEADER_TRADACOMS_T:
- case INTERCHANGE_CANDIDATE:
- case HEADER_DATA:
- case HEADER_ELEMENT_END:
- case HEADER_COMPONENT_END:
- case HEADER_SEGMENT_END:
- writeHeader((char) output, isPrettyPrint);
- break;
- case INVALID:
- throw new EDIException(String.format("Invalid state: %s; output 0x%04X", state, output));
- default:
- writeOutput(output);
- break;
- }
- }
-
- void writeHeader(char output, boolean isPrettyPrint) throws EDIStreamException {
- if (!isPrettyPrint && !dialect.appendHeader(characters, output)) {
- throw new EDIStreamException(String.format("Failed writing %s header: %s", dialect.getStandard(), dialect.getRejectionMessage()));
- }
-
- unconfirmedBuffer.append(output);
-
- if (dialect.isConfirmed()) {
- // Set up the delimiters again once the dialect has confirmed them
- setupDelimiters();
-
- // Switching to non-header states to proceed after dialect is confirmed
- switch (state) {
- case HEADER_DATA:
- state = State.TAG_SEARCH;
- break;
- case HEADER_ELEMENT_END:
- state = State.ELEMENT_END;
- break;
- case HEADER_SEGMENT_END:
- state = State.SEGMENT_END;
- break;
- default:
- throw new IllegalStateException("Confirmed at state " + state);
- }
-
- unconfirmedBuffer.flip();
-
- if (EDIFACTDialect.UNA.equals(dialect.getHeaderTag())) {
- // Overlay the UNA segment repetition separator now that it has be confirmed
- unconfirmedBuffer.put(7, this.repetitionSeparator > 0 ? this.repetitionSeparator : ' ');
- }
-
- while (unconfirmedBuffer.hasRemaining()) {
- writeOutput(unconfirmedBuffer.get());
- }
- }
- }
-
- void writeOutput(int output) throws EDIStreamException {
+// private Optional validator() {
+// Validator validator;
+//
+// // Do not use the transactionValidator in the period where it may be set/mutated by the user
+// if (transaction && !transactionSchemaAllowed) {
+// validator = transactionValidator;
+// } else {
+// validator = controlValidator;
+// }
+//
+// return Optional.ofNullable(validator);
+// }
+
+// private void write(int output) throws EDIStreamException {
+// write(output, false);
+// }
+//
+// private void write(int output, boolean isPrettyPrint) throws EDIStreamException {
+// CharacterClass clazz;
+//
+// clazz = characters.getClass(output);
+//
+// if (clazz == CharacterClass.INVALID) {
+// throw new EDIStreamException(String.format("Invalid character: 0x%04X", output), location);
+// }
+//
+// state = State.transition(state, dialect, clazz);
+//
+// switch (state) {
+// case HEADER_X12_I: // I(SA)
+// case HEADER_EDIFACT_U: // U(NA) or U(NB)
+// case HEADER_TRADACOMS_S: // S(TX)
+// unconfirmedBuffer.clear();
+// writeHeader((char) output, isPrettyPrint);
+// break;
+// case HEADER_X12_S:
+// case HEADER_EDIFACT_N:
+// case HEADER_TRADACOMS_T:
+// case INTERCHANGE_CANDIDATE:
+// case HEADER_DATA:
+// case HEADER_ELEMENT_END:
+// case HEADER_COMPONENT_END:
+// case HEADER_SEGMENT_END:
+// writeHeader((char) output, isPrettyPrint);
+// break;
+// case INVALID:
+// throw new EDIException(String.format("Invalid state: %s; output 0x%04X", state, output));
+// default:
+// writeOutput(output);
+// break;
+// }
+// }
+//
+// void writeHeader(char output, boolean isPrettyPrint) throws EDIStreamException {
+// if (!isPrettyPrint && !dialect.appendHeader(characters, output)) {
+// throw new EDIStreamException(String.format("Failed writing %s header: %s", dialect.getStandard(), dialect.getRejectionMessage()));
+// }
+//
+// unconfirmedBuffer.append(output);
+//
+// if (dialect.isConfirmed()) {
+// // Set up the delimiters again once the dialect has confirmed them
+// setupDelimiters(dialect);
+//
+// // Switching to non-header states to proceed after dialect is confirmed
+// switch (state) {
+// case HEADER_DATA:
+// state = State.TAG_SEARCH;
+// break;
+// case HEADER_ELEMENT_END:
+// state = State.ELEMENT_END;
+// break;
+// case HEADER_SEGMENT_END:
+// state = State.SEGMENT_END;
+// break;
+// default:
+// throw new IllegalStateException("Confirmed at state " + state);
+// }
+//
+// unconfirmedBuffer.flip();
+//
+// if (EDIFACTDialect.UNA.equals(dialect.getHeaderTag())) {
+// // Overlay the UNA segment repetition separator now that it has be confirmed
+// unconfirmedBuffer.put(7, this.repetitionSeparator > 0 ? this.repetitionSeparator : ' ');
+// }
+//
+// while (unconfirmedBuffer.hasRemaining()) {
+// writeOutput(unconfirmedBuffer.get());
+// }
+// }
+// }
+//
+// void writeOutput(int output) throws EDIStreamException {
+// try {
+// location.incrementOffset(output);
+// outputBuffer.append((char) output);
+// //writer.write(output);
+// } catch (Exception e) {
+// throw new EDIStreamException("Exception to output stream", location, e);
+// }
+// }
+
+ void write(int output) throws EDIStreamException {
try {
- location.incrementOffset(output);
- writer.write(output);
- } catch (IOException e) {
- throw new EDIStreamException("Exception to output stream", location, e);
+// location.incrementOffset(output);
+ outputBuffer.append((char) output);
+ //writer.write(output);
+ } catch (Exception e) {
+ throw new EDIStreamException("Exception writing to output stream", location, e);
}
}
@Override
public EDIStreamWriter startInterchange() {
ensureLevel(LEVEL_INITIAL);
- ensureState(State.INITIAL);
+ //ensureState(State.INITIAL);
level = LEVEL_INTERCHANGE;
if (controlSchema == null) {
LOGGER.warning("Starting interchange without control structure validation. See EDIStreamWriter#setControlSchema");
@@ -409,48 +412,64 @@ public EDIStreamWriter endInterchange() throws EDIStreamException {
ensureLevel(LEVEL_INTERCHANGE);
level = LEVEL_INITIAL;
flush();
+ dialectHeaderReceived = false;
+ dialectHeaderProcessed = false;
+ location = null;
+ dialect = null;
return this;
}
@Override
public EDIStreamWriter writeStartSegment(String name) throws EDIStreamException {
ensureLevel(LEVEL_INTERCHANGE);
- location.incrementSegmentPosition(name);
+// location.incrementSegmentPosition(name);
+
+ if (!dialectHeaderReceived) {
+ dialect = dialect != null ? dialect : DialectFactory.getDialect(name);
- if (state == State.INITIAL) {
- dialect = DialectFactory.getDialect(name);
- setupDelimiters();
+ location = lexer.getLocation().copy();
+ location.incrementSegmentPosition(name);
+
+ setupDelimiters(dialect);
if (dialect instanceof EDIFACTDialect) {
- if (EDIFACTDialect.UNB.equals(name) && areDelimitersSpecified()) {
- /*
- * Writing the EDIFACT header when delimiters were given via properties requires that
- * a UNA is written first.
- */
- dialect = DialectFactory.getDialect(EDIFACTDialect.UNA);
- writeServiceAdviceString();
- segmentValidation(name);
+ if (EDIFACTDialect.UNB.equals(name)) {
+// /*
+// * Writing the EDIFACT header when delimiters were given via properties requires that
+// * a UNA is written first.
+// */
+// dialect = DialectFactory.getDialect(EDIFACTDialect.UNA);
+ if (EDIFACTDialect.UNB.equals(dialect.getHeaderTag()) && areDelimitersSpecified()) {
+ writeServiceAdviceString();
+ }
+ //segmentValidation(name);
// Now write the UNB
writeString(name);
+ dialectHeaderReceived = true;
+ } else if (EDIFACTDialect.UNA.equals(name)) {
+ writeString(name);
+ writeServiceAdviceCharacters();
} else {
- if (EDIFACTDialect.UNA.equals(name)) {
- writeString(name);
- writeServiceAdviceCharacters();
- } else {
- segmentValidation(name);
- writeString(name);
- }
+ // Unexpected header segment
+ writeString(name);
+ dialectHeaderReceived = true;
}
} else {
- segmentValidation(name);
+ //segmentValidation(name);
writeString(name);
+ dialectHeaderReceived = true;
}
} else {
- segmentValidation(name);
+ //segmentValidation(name);
writeString(name);
+ if (segmentTagTerminator != '\0') {
+ signalElementDataCompleteEvent(segmentTagTerminator);
+ } else {
+ signalElementDataCompleteEvent(dataElementSeparator);
+ }
}
- countSegment(name);
+ //countSegment(name);
level = LEVEL_SEGMENT;
emptyElements = 0;
terminateSegmentTag();
@@ -458,20 +477,20 @@ public EDIStreamWriter writeStartSegment(String name) throws EDIStreamException
return this;
}
- void countSegment(String name) {
- if (controlValidator != null) {
- controlValidator.countSegment(name);
- }
- }
+// void countSegment(String name) {
+// if (controlValidator != null) {
+// controlValidator.countSegment(name);
+// }
+// }
- void segmentValidation(String name) {
- validate(validator -> validator.validateSegment(this, name));
-
- if (exitTransaction(name)) {
- transaction = false;
- validate(validator -> validator.validateSegment(this, name));
- }
- }
+// 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') {
@@ -487,7 +506,7 @@ void terminateSegmentTag() throws EDIStreamException {
void writeServiceAdviceString() throws EDIStreamException {
writeString(EDIFACTDialect.UNA);
writeServiceAdviceCharacters();
- writeSegmentTerminator();
+ writeDelimiter(Delimiters.SEGMENT);
}
void writeServiceAdviceCharacters() throws EDIStreamException {
@@ -505,51 +524,83 @@ private void writeString(String value) throws EDIStreamException {
}
}
- void writeSegmentTerminator() throws EDIStreamException {
- write(this.segmentTerminator);
+ void writeDelimiter(String delimiterType) throws EDIStreamException {
+ boolean invokeErrorDetection = true;
- if (prettyPrint) {
- for (int i = 0, m = prettyPrintString.length(); i < m; i++) {
- write(prettyPrintString.charAt(i), true);
+ switch (delimiterType) {
+ case Delimiters.RELEASE:
+ write(this.releaseIndicator);
+ invokeErrorDetection = false;
+ break;
+ case Delimiters.COMPONENT_ELEMENT:
+ write(this.componentElementSeparator);
+ invokeErrorDetection = dialectHeaderProcessed;
+ break;
+ case Delimiters.DATA_ELEMENT:
+ location.setRepeating(false);
+ write(this.dataElementSeparator);
+ invokeErrorDetection = dialectHeaderProcessed;
+ break;
+ case Delimiters.REPETITION:
+ location.setRepeating(true);
+ write(this.repetitionSeparator);
+ invokeErrorDetection = dialectHeaderProcessed;
+ break;
+ case Delimiters.SEGMENT:
+ location.setRepeating(false);
+ write(this.segmentTerminator);
+ invokeErrorDetection = dialectHeaderReceived;
+ if (prettyPrint) {
+ for (int i = 0, m = prettyPrintString.length(); i < m; i++) {
+ write(prettyPrintString.charAt(i)/*, true*/);
+ }
}
+ break;
+ default:
+ // unexpected
+ break;
}
- }
- boolean exitTransaction(CharSequence tag) {
- return transaction && !transactionSchemaAllowed && controlSchema != null
- && controlSchema.containsSegment(tag.toString());
+ if (invokeErrorDetection) {
+ errorDetection();
+ }
}
+// boolean exitTransaction(CharSequence tag) {
+// return transaction && !transactionSchemaAllowed && controlSchema != null
+// && controlSchema.containsSegment(tag.toString());
+// }
+
@Override
public EDIStreamWriter writeEndSegment() throws EDIStreamException {
ensureLevelAtLeast(LEVEL_SEGMENT);
- 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) {
- state = State.ELEMENT_END_BINARY;
- }
-
- writeSegmentTerminator();
-
- switch (state) {
- case SEGMENT_END:
- case HEADER_SEGMENT_END:
- case INITIAL: // Ending final segment of the interchange
- break;
- default:
- if (state.isHeaderState() && dialect instanceof X12Dialect) {
- throw new EDIStreamException("Invalid X12 ISA segment: too short or elements missing");
- }
- break;
- }
+// 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) {
+// state = State.ELEMENT_END_BINARY;
+// }
+
+ writeDelimiter(Delimiters.SEGMENT);
+
+// switch (state) {
+// case SEGMENT_END:
+// case HEADER_SEGMENT_END:
+// case INITIAL: // Ending final segment of the interchange
+// break;
+// default:
+// if (state.isHeaderState() && dialect instanceof X12Dialect) {
+// throw new EDIStreamException("Invalid X12 ISA segment: too short or elements missing");
+// }
+// break;
+// }
level = LEVEL_INTERCHANGE;
- location.clearSegmentLocations();
- transactionSchemaAllowed = false;
+// location.clearSegmentLocations();
+// transactionSchemaAllowed = false;
return this;
}
@@ -558,14 +609,16 @@ public EDIStreamWriter writeEndSegment() throws EDIStreamException {
public EDIStreamWriter writeStartElement() throws EDIStreamException {
ensureLevel(LEVEL_SEGMENT);
level = LEVEL_ELEMENT;
- location.incrementElementPosition();
- elementBuffer.clear();
+ if (!dialectHeaderProcessed) {
+ location.incrementElement(false);
+ }
+ //elementBuffer.clear();
elementLength = 0;
emptyComponents = 0;
unterminatedComponent = false;
if (!emptyElementTruncation && unterminatedElement) {
- write(this.dataElementSeparator);
+ writeDelimiter(Delimiters.DATA_ELEMENT);
}
return this;
@@ -574,18 +627,25 @@ public EDIStreamWriter writeStartElement() throws EDIStreamException {
@Override
public EDIStreamWriter writeStartElementBinary() throws EDIStreamException {
writeStartElement();
- state = State.ELEMENT_DATA_BINARY;
+// state = State.ELEMENT_DATA_BINARY;
+ writeRequiredSeparators(1);
+ errorDetection();
+ flush(); // Write `Writer` buffers to stream before writing binary
+ // From the Lexer's perspective, writing a binary element will look like an empty element
+ inBinaryElement = true;
+ location.incrementElement(false);
return this;
}
@Override
public EDIStreamWriter writeRepeatElement() throws EDIStreamException {
ensureLevelAtLeast(LEVEL_SEGMENT);
- write(this.repetitionSeparator);
+ // TODO: test writeRequiredSeparators(1);
+ writeDelimiter(Delimiters.REPETITION);
// The repetition separator was used instead of the data element separator
unterminatedElement = false;
level = LEVEL_ELEMENT;
- location.incrementElementOccurrence();
+
elementLength = 0;
emptyComponents = 0;
unterminatedComponent = false;
@@ -596,16 +656,21 @@ public EDIStreamWriter writeRepeatElement() throws EDIStreamException {
public EDIStreamWriter endElement() throws EDIStreamException {
ensureLevelAtLeast(LEVEL_ELEMENT);
- if (!atomicElementWrite) {
- if (level > LEVEL_ELEMENT) {
- validate(validator -> validator.validateSyntax(dialect, this, this, location, true));
- } else {
- validateElement(this.elementBuffer::flip, this.elementBuffer);
- }
+// if (!atomicElementWrite) {
+// if (level > LEVEL_ELEMENT) {
+// validate(validator -> validator.validateSyntax(dialect, this, this, location, true));
+// } else {
+// validateElement(this.elementBuffer::flip, this.elementBuffer);
+// }
+// }
+
+ signalElementDataCompleteEvent(dataElementSeparator);
+ if (!dialectHeaderProcessed) {
+ location.clearComponentPosition();
}
- location.clearComponentPosition();
level = LEVEL_SEGMENT;
+ inBinaryElement = false;
if (elementLength > 0) {
unterminatedElement = true;
@@ -613,9 +678,9 @@ public EDIStreamWriter endElement() throws EDIStreamException {
emptyElements++;
}
- if (state == State.ELEMENT_DATA_BINARY) {
- state = State.ELEMENT_END_BINARY;
- }
+// if (state == State.ELEMENT_DATA_BINARY) {
+// state = State.ELEMENT_END_BINARY;
+// }
return this;
}
@@ -623,20 +688,24 @@ public EDIStreamWriter endElement() throws EDIStreamException {
@Override
public EDIStreamWriter startComponent() throws EDIStreamException {
ensureLevelBetween(LEVEL_ELEMENT, LEVEL_COMPOSITE);
- ensureFalse(state == State.ELEMENT_DATA_BINARY);
+ ensureFalse(inBinaryElement);
- if (LEVEL_ELEMENT == level) {
- // Level is LEVEL_ELEMENT only for the first component
- validateCompositeOccurrence();
- }
+// if (LEVEL_ELEMENT == level) {
+// // Level is LEVEL_ELEMENT only for the first component
+// validateCompositeOccurrence();
+// }
if (LEVEL_COMPOSITE == level && !emptyElementTruncation) {
- write(this.componentElementSeparator);
+ writeDelimiter(Delimiters.COMPONENT_ELEMENT);
}
level = LEVEL_COMPONENT;
- location.incrementComponentPosition();
- elementBuffer.clear();
+ if (!dialectHeaderProcessed) {
+ location.setComposite(true);
+ location.incrementElement(false);
+ }
+// location.incrementComponentPosition();
+ //elementBuffer.clear();
elementLength = 0;
return this;
}
@@ -645,9 +714,9 @@ public EDIStreamWriter startComponent() throws EDIStreamException {
public EDIStreamWriter endComponent() throws EDIStreamException {
ensureLevel(LEVEL_COMPONENT);
- if (!atomicElementWrite) {
- validateElement(this.elementBuffer::flip, this.elementBuffer);
- }
+// if (!atomicElementWrite) {
+// validateElement(this.elementBuffer::flip, this.elementBuffer);
+// }
if (elementLength > 0) {
unterminatedComponent = true;
@@ -655,73 +724,79 @@ public EDIStreamWriter endComponent() throws EDIStreamException {
emptyComponents++;
}
+ signalElementDataCompleteEvent(componentElementSeparator);
level = LEVEL_COMPOSITE;
+ inBinaryElement = false;
return this;
}
@Override
public EDIStreamWriter writeElement(CharSequence text) throws EDIStreamException {
- atomicElementWrite = true;
+// atomicElementWrite = true;
writeStartElement();
- CharSequence value = validateElement(() -> {}, text);
- writeElementData(value);
+// CharSequence value = validateElement(() -> {}, text);
+// writeElementData(value);
+ writeElementData(text);
endElement();
- atomicElementWrite = false;
+// atomicElementWrite = false;
return this;
}
@Override
public EDIStreamWriter writeElement(char[] text, int start, int end) throws EDIStreamException {
- atomicElementWrite = true;
+// atomicElementWrite = true;
writeStartElement();
- CharSequence value = validateElement(() -> dataHolder.set(text, start, start + end), dataHolder);
- writeElementData(value);
+// CharSequence value = validateElement(() -> dataHolder.set(text, start, start + end), dataHolder);
+// writeElementData(value);
+ writeElementData(text, start, end);
endElement();
- atomicElementWrite = false;
+// atomicElementWrite = false;
return this;
}
@Override
public EDIStreamWriter writeEmptyElement() throws EDIStreamException {
- atomicElementWrite = true;
+// atomicElementWrite = true;
writeStartElement();
// Ignore possibly-formatted value
- validateElement(dataHolder::clear, dataHolder);
+// validateElement(dataHolder::clear, dataHolder);
endElement();
- atomicElementWrite = false;
+// atomicElementWrite = false;
return this;
}
@Override
public EDIStreamWriter writeComponent(CharSequence text) throws EDIStreamException {
- atomicElementWrite = true;
+// atomicElementWrite = true;
startComponent();
- CharSequence value = validateElement(() -> {}, text);
- writeElementData(value);
+// CharSequence value = validateElement(() -> {}, text);
+// writeElementData(value);
+ writeElementData(text);
endComponent();
- atomicElementWrite = false;
+// atomicElementWrite = false;
return this;
}
@Override
public EDIStreamWriter writeComponent(char[] text, int start, int end) throws EDIStreamException {
- atomicElementWrite = true;
+// atomicElementWrite = true;
startComponent();
- CharSequence value = validateElement(() -> dataHolder.set(text, start, start + end), dataHolder);
- writeElementData(value);
+// CharSequence value = validateElement(() -> dataHolder.set(text, start, start + end), dataHolder);
+// writeElementData(value);
+ writeElementData(text, start, end);
endComponent();
- atomicElementWrite = false;
+// atomicElementWrite = false;
return this;
}
@Override
public EDIStreamWriter writeEmptyComponent() throws EDIStreamException {
- atomicElementWrite = true;
+// atomicElementWrite = true;
startComponent();
// Ignore possibly-formatted value
- validateElement(dataHolder::clear, dataHolder);
+// validateElement(dataHolder::clear, dataHolder);
endComponent();
- atomicElementWrite = false;
+// atomicElementWrite = false;
return this;
}
@@ -730,24 +805,24 @@ void writeRequiredSeparators(int dataLength) throws EDIStreamException {
return;
}
- writeRequiredSeparator(emptyElements, unterminatedElement, this.dataElementSeparator);
+ writeRequiredSeparator(emptyElements, unterminatedElement, Delimiters.DATA_ELEMENT);
emptyElements = 0;
unterminatedElement = false;
if (level == LEVEL_COMPONENT) {
- writeRequiredSeparator(emptyComponents, unterminatedComponent, this.componentElementSeparator);
+ writeRequiredSeparator(emptyComponents, unterminatedComponent, Delimiters.COMPONENT_ELEMENT);
emptyComponents = 0;
unterminatedComponent = false;
}
}
- void writeRequiredSeparator(int emptyCount, boolean unterminated, char separator) throws EDIStreamException {
+ void writeRequiredSeparator(int emptyCount, boolean unterminated, String delimiterType) throws EDIStreamException {
for (int i = 0; i < emptyCount; i++) {
- write(separator);
+ writeDelimiter(delimiterType);
}
if (unterminated) {
- write(separator);
+ writeDelimiter(delimiterType);
}
}
@@ -759,16 +834,16 @@ public EDIStreamWriter writeElementData(CharSequence text) throws EDIStreamExcep
for (int i = 0, m = text.length(); i < m; i++) {
char curr = text.charAt(i);
- if (characters.isDelimiter(curr)) {
+ if (lexer.getCharacterSet().isDelimiter(curr)) {
if (releaseIndicator > 0) {
- write(releaseIndicator);
+ writeDelimiter(Delimiters.RELEASE);
} else {
throw new IllegalArgumentException("Value contains separator: " + curr);
}
}
write(curr);
- elementBuffer.put(curr);
+ //elementBuffer.put(curr);
elementLength++;
}
return this;
@@ -782,11 +857,11 @@ public EDIStreamWriter writeElementData(char[] text, int start, int end) throws
for (int i = start, m = end; i < m; i++) {
char curr = text[i];
- if (characters.isDelimiter(curr)) {
+ if (lexer.getCharacterSet().isDelimiter(curr)) {
throw new IllegalArgumentException("Value contains separator");
}
write(curr);
- elementBuffer.put(curr);
+ //elementBuffer.put(curr);
elementLength++;
}
@@ -796,14 +871,10 @@ public EDIStreamWriter writeElementData(char[] text, int start, int end) throws
@Override
public EDIStreamWriter writeBinaryData(InputStream binaryStream) throws EDIStreamException {
ensureLevel(LEVEL_ELEMENT);
- ensureState(State.ELEMENT_DATA_BINARY);
+ ensureFalse(!inBinaryElement);
int output;
try {
- writeRequiredSeparators(binaryStream.available());
-
- flush(); // Write `Writer` buffers to stream before writing binary
-
while ((output = binaryStream.read()) != -1) {
location.incrementOffset(output);
stream.write(output);
@@ -819,13 +890,10 @@ public EDIStreamWriter writeBinaryData(InputStream binaryStream) throws EDIStrea
@Override
public EDIStreamWriter writeBinaryData(byte[] binary, int start, int end) throws EDIStreamException {
ensureLevel(LEVEL_ELEMENT);
- ensureState(State.ELEMENT_DATA_BINARY);
+ ensureFalse(!inBinaryElement);
ensureArgs(binary.length, start, end);
- writeRequiredSeparators(end - start);
try {
- flush(); // Write `Writer` buffers to stream before writing binary
-
for (int i = start; i < end; i++) {
location.incrementOffset(binary[i]);
stream.write(binary[i]);
@@ -841,166 +909,277 @@ public EDIStreamWriter writeBinaryData(byte[] binary, int start, int end) throws
@Override
public EDIStreamWriter writeBinaryData(ByteBuffer binary) throws EDIStreamException {
ensureLevel(LEVEL_ELEMENT);
- ensureState(State.ELEMENT_DATA_BINARY);
- writeRequiredSeparators(binary.remaining());
+ ensureFalse(!inBinaryElement);
- while (binary.hasRemaining()) {
- write(binary.get());
- elementLength++;
+ try {
+ while (binary.hasRemaining()) {
+ byte out = binary.get();
+ location.incrementOffset(out);
+ stream.write(out);
+ elementLength++;
+ }
+ } catch (IOException e) {
+ throw new EDIStreamException("Exception writing binary element data", location, e);
}
return this;
}
- @Override
- public boolean binaryData(InputStream binary) {
- // No operation
- return true;
- }
-
- @Override
- public boolean elementData(CharSequence text, boolean fromStream) {
- if (level > LEVEL_ELEMENT) {
- location.incrementComponentPosition();
- } else {
- location.incrementElementPosition();
+ void signalElementDataCompleteEvent(int delimiter) throws EDIStreamException {
+ if (dialectHeaderProcessed) {
+ errorDetection();
+ lexer.signalElementDataCompleteEvent(delimiter);
+ outputBuffer.flip();
+ processProxyEvents();
+ outputBuffer.clear();
}
+ }
- dialect.elementData(elementHolder, location);
+ void errorDetection() throws EDIStreamException {
+ errors.clear();
+ outputBuffer.flip();
+ lexer.parse(outputBuffer);
+ processProxyEvents();
- validator().ifPresent(validator -> {
- if (!validator.validateElement(dialect, location, elementHolder, null)) {
- reportElementErrors(validator, elementHolder);
- }
- });
+ outputBuffer.rewind();
- return true;
- }
+ if (!dialectHeaderProcessed) {
+ dialect = lexer.getDialect();
+ location = lexer.getLocation();
- @Override
- public void loopBegin(EDIReference typeReference) {
- final String loopCode = typeReference.getReferencedType().getCode();
-
- if (EDIType.Type.TRANSACTION.toString().equals(loopCode)) {
- transaction = true;
- transactionSchemaAllowed = true;
- if (transactionValidator != null) {
- transactionValidator.reset();
+ if (!dialect.isConfirmed()) {
+ throw new EDIStreamException("Header data could not be processed as a valid " + dialect.getStandard() + " header. Data: [" + outputBuffer + "]");
}
- }
- }
- @Override
- public void loopEnd(EDIReference typeReference) {
- final String loopCode = typeReference.getReferencedType().getCode();
+ setupDelimiters(dialect);
- if (EDIType.Type.TRANSACTION.toString().equals(loopCode)) {
- transaction = false;
- dialect.transactionEnd();
- } else if (EDIType.Type.GROUP.toString().equals(loopCode)) {
- dialect.groupEnd();
- }
- }
+ if (EDIFACTDialect.UNA.equals(dialect.getHeaderTag())) {
+ // Overlay the UNA segment repetition separator now that it has be confirmed
+ outputBuffer.put(7, this.repetitionSeparator > 0 ? this.repetitionSeparator : ' ');
+ }
- @Override
- public void elementError(EDIStreamEvent event,
- EDIStreamValidationError error,
- EDIReference typeReference,
- CharSequence data,
- int element,
- int component,
- int repetition) {
-
- StaEDIStreamLocation copy = location.copy();
- copy.setElementPosition(element);
- copy.setElementOccurrence(repetition);
- copy.setComponentPosition(component);
-
- if (this.reporter != null) {
- this.reporter.report(error, this, copy, data, typeReference);
- } else {
- errors.add(new EDIValidationException(event, error, copy, data));
+ dialectHeaderProcessed = true;
}
- }
- @Override
- public void segmentError(CharSequence token, EDIReference typeReference, EDIStreamValidationError error) {
- if (this.reporter != null) {
- this.reporter.report(error, this, this.getLocation(), token, typeReference);
- } else {
- errors.add(new EDIValidationException(EDIStreamEvent.SEGMENT_ERROR, error, location, token));
+ try {
+ writer.write(outputBuffer.array(), outputBuffer.arrayOffset(), outputBuffer.length());
+ } catch (IOException e) {
+ throw new EDIStreamException("", location, e);
}
+ outputBuffer.clear();
}
- private void validate(Consumer command) {
- validator().ifPresent(validator -> {
- errors.clear();
- command.accept(validator);
-
- if (!errors.isEmpty()) {
- throw validationExceptionChain(errors);
+ void processProxyEvents() throws EDIStreamException {
+ errors.clear();
+ EDIStreamEvent event;
+
+ while ((event = nextEvent()) != null) {
+ if (EDIStreamEvent.START_INTERCHANGE == event && useInternalControlSchema()) {
+ try {
+ LOGGER.finer(() -> "Setting control schema: " + lexer.getDialect().getStandard() + ", " + lexer.getDialect().getVersion());
+ this.controlSchema = SchemaUtils.getControlSchema(lexer.getDialect().getStandard(), lexer.getDialect().getVersion());
+ proxy.setControlSchema(controlSchema, true);
+ LOGGER.finer(() -> "Done setting control schema: " + lexer.getDialect().getStandard() + ", " + lexer.getDialect().getVersion());
+ } catch (EDISchemaException e) {
+ LOGGER.log(Level.WARNING,
+ String.format("Exception loading controlSchema for standard %s, version %s: %s",
+ getStandard(),
+ Arrays.stream(lexer.getDialect().getVersion()).map(Object::toString)
+ .collect(Collectors.joining(", ")),
+ e.getMessage()),
+ e);
+ }
}
- });
- }
- private void validateCompositeOccurrence() {
- validator().ifPresent(validator -> {
- errors.clear();
+ if (event.isError()) {
+ Location errLocation = proxy.getLocation().copy();
+ EDIStreamValidationError error = proxy.getErrorType();
+ CharSequence data = proxy.getCharacters(); //.toString();
+ data = data != null ? data.toString() : null;
- if (!validator.validCompositeOccurrences(dialect, location)) {
- reportElementErrors(validator, "");
- }
-
- if (!errors.isEmpty()) {
- throw validationExceptionChain(errors);
+ if (this.reporter != null) {
+ this.reporter.report(error, this, errLocation, data, proxy.getSchemaTypeReference());
+ } else {
+ errors.add(new EDIValidationException(event, error, errLocation, data));
+ }
}
- });
- }
-
- private CharSequence validateElement(Runnable setupCommand, CharSequence data) {
- return validator()
- .map(validator -> validateElement(setupCommand, data, validator))
- .orElse(data);
- }
-
- CharSequence validateElement(Runnable setupCommand, CharSequence data, Validator validator) {
- CharSequence elementData;
-
- if (this.formatElements) {
- elementData = this.formattedElement;
- this.formattedElement.setLength(0);
- this.formattedElement.append(data); // Validator will clear and re-format if configured
- } else {
- elementData = data;
- }
- errors.clear();
- setupCommand.run();
-
- if (!validator.validateElement(dialect, location, data, this.formattedElement)) {
- reportElementErrors(validator, elementData);
+ advanceProxyQueue();
}
if (!errors.isEmpty()) {
throw validationExceptionChain(errors);
}
+ }
- dialect.elementData(elementData, location);
- return elementData;
+ EDIStreamEvent nextEvent() throws EDIException {
+ EDIStreamEvent event = proxy.getEvent();
+
+ if (event == null) {
+ advanceProxyQueue();
+ event = proxy.getEvent();
+ }
+
+ return event;
}
- 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());
+ void advanceProxyQueue() throws EDIException {
+ if (!proxy.nextEvent()) {
+ proxy.resetEvents();
+ lexer.parse(outputBuffer);
}
}
+// @Override
+// public boolean binaryData(InputStream binary) {
+// // No operation
+// return true;
+// }
+//
+// @Override
+// public boolean elementData(char[] text, int start, int length) {
+// if (level > LEVEL_ELEMENT) {
+// location.incrementComponentPosition();
+// } else {
+// location.incrementElementPosition();
+// }
+//
+// elementHolder.set(text, start, length);
+// dialect.elementData(elementHolder, location);
+//
+// validator().ifPresent(validator -> {
+// if (!validator.validateElement(dialect, location, elementHolder, null)) {
+// reportElementErrors(validator, elementHolder);
+// }
+// });
+//
+// return true;
+// }
+//
+// @Override
+// public void loopBegin(EDIReference typeReference) {
+// final String loopCode = typeReference.getReferencedType().getCode();
+//
+// if (EDIType.Type.TRANSACTION.toString().equals(loopCode)) {
+// transaction = true;
+// transactionSchemaAllowed = true;
+// if (transactionValidator != null) {
+// transactionValidator.reset();
+// }
+// }
+// }
+//
+// @Override
+// public void loopEnd(EDIReference typeReference) {
+// final String loopCode = typeReference.getReferencedType().getCode();
+//
+// if (EDIType.Type.TRANSACTION.toString().equals(loopCode)) {
+// transaction = false;
+// dialect.transactionEnd();
+// } else if (EDIType.Type.GROUP.toString().equals(loopCode)) {
+// dialect.groupEnd();
+// }
+// }
+//
+// @Override
+// public void elementError(EDIStreamEvent event,
+// EDIStreamValidationError error,
+// EDIReference typeReference,
+// CharSequence data,
+// int element,
+// int component,
+// int repetition) {
+//
+// StaEDIStreamLocation copy = location.copy();
+// copy.setElementPosition(element);
+// copy.setElementOccurrence(repetition);
+// copy.setComponentPosition(component);
+//
+// if (this.reporter != null) {
+// this.reporter.report(error, this, copy, data, typeReference);
+// } else {
+// errors.add(new EDIValidationException(event, error, copy, data));
+// }
+// }
+//
+// @Override
+// public void segmentError(CharSequence token, EDIReference typeReference, EDIStreamValidationError error) {
+// if (this.reporter != null) {
+// this.reporter.report(error, this, this.getLocation(), token, typeReference);
+// } else {
+// errors.add(new EDIValidationException(EDIStreamEvent.SEGMENT_ERROR, error, location, token));
+// }
+// }
+//
+// private void validate(Consumer command) {
+// validator().ifPresent(validator -> {
+// errors.clear();
+// command.accept(validator);
+//
+// if (!errors.isEmpty()) {
+// throw validationExceptionChain(errors);
+// }
+// });
+// }
+//
+// private void validateCompositeOccurrence() {
+// validator().ifPresent(validator -> {
+// errors.clear();
+//
+// if (!validator.validCompositeOccurrences(dialect, location)) {
+// reportElementErrors(validator, "");
+// }
+//
+// if (!errors.isEmpty()) {
+// throw validationExceptionChain(errors);
+// }
+// });
+// }
+//
+// private CharSequence validateElement(Runnable setupCommand, CharSequence data) {
+// return validator()
+// .map(validator -> validateElement(setupCommand, data, validator))
+// .orElse(data);
+// }
+//
+// CharSequence validateElement(Runnable setupCommand, CharSequence data, Validator validator) {
+// CharSequence elementData;
+//
+// if (this.formatElements) {
+// elementData = this.formattedElement;
+// this.formattedElement.setLength(0);
+// this.formattedElement.append(data); // Validator will clear and re-format if configured
+// } else {
+// elementData = data;
+// }
+//
+// errors.clear();
+// setupCommand.run();
+//
+// if (!validator.validateElement(dialect, location, data, this.formattedElement)) {
+// reportElementErrors(validator, elementData);
+// }
+//
+// if (!errors.isEmpty()) {
+// throw validationExceptionChain(errors);
+// }
+//
+// dialect.elementData(elementData, location);
+// return elementData;
+// }
+//
+// 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();
@@ -1014,4 +1193,12 @@ EDIValidationException validationExceptionChain(List err
return first;
}
+
+ boolean useInternalControlSchema() {
+ if (this.controlSchema != null) {
+ return false;
+ }
+
+ return getProperty(EDIInputFactory.EDI_VALIDATE_CONTROL_STRUCTURE, Boolean::parseBoolean, false);
+ }
}
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 68a1be70..f1f74d48 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
@@ -15,12 +15,12 @@
******************************************************************************/
package io.xlate.edi.internal.stream.tokenization;
-import io.xlate.edi.stream.EDIStreamException;
-import io.xlate.edi.stream.Location;
-
import java.util.HashMap;
import java.util.Map;
+import io.xlate.edi.stream.EDIStreamException;
+import io.xlate.edi.stream.Location;
+
public class EDIException extends EDIStreamException {
private static final long serialVersionUID = -2724168743697298348L;
@@ -41,7 +41,7 @@ public class EDIException extends EDIStreamException {
exceptionMessages.put(INVALID_STATE,
"EDIE003 - Invalid processing state");
exceptionMessages.put(INVALID_CHARACTER,
- "EDIE004 - Invalid input character");
+ "EDIE004 - Invalid character");
exceptionMessages.put(INCOMPLETE_STREAM,
"EDIE005 - Unexpected end of stream");
}
@@ -55,7 +55,7 @@ public EDIException(String message) {
}
EDIException(Integer id, String message, Location location) {
- super(exceptionMessages.get(id) + "; " + message, location);
+ super(buildMessage(exceptionMessages.get(id), location) + "; " + message, location);
}
public EDIException(Integer id, String message) {
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 d15fd530..582cf8dc 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
@@ -45,9 +45,10 @@ private enum Mode {
}
private final Deque modes = new ArrayDeque<>();
- private int input = 0;
private State state = State.INITIAL;
+ private int previousInput = 0;
private State previous;
+ private boolean dataEventNotified = false;
private interface Notifier {
boolean execute(State state, int start, int length);
@@ -107,6 +108,10 @@ public Lexer(InputStream stream, Charset charset, EventHandler handler, StaEDISt
};
ssn = (notifyState, start, length) -> {
+ if (dataEventNotified) {
+ dataEventNotified = false;
+ return false;
+ }
String segmentTag = new String(buffer.array(), start, length);
return handler.segmentBegin(segmentTag);
};
@@ -117,6 +122,10 @@ public Lexer(InputStream stream, Charset charset, EventHandler handler, StaEDISt
bn = (notifyState, start, length) -> handler.binaryData(binaryStream);
en = (notifyState, start, length) -> {
+ if (dataEventNotified) {
+ dataEventNotified = false;
+ return false;
+ }
elementHolder.set(buffer.array(), start, length);
return handler.elementData(elementHolder, true);
};
@@ -127,6 +136,14 @@ public Dialect getDialect() {
return dialect;
}
+ public StaEDIStreamLocation getLocation() {
+ return location;
+ }
+
+ public CharacterSet getCharacterSet() {
+ return characters;
+ }
+
public void invalidate() {
if (state != State.INVALID) {
previous = state;
@@ -171,141 +188,188 @@ public boolean hasRemaining() throws IOException {
public void parse() throws IOException, EDIException {
try {
- parse(this::readCharacterUnchecked);
+ parse(this::readCharacterUnchecked, false);
} catch (UncheckedIOException e) {
throw e.getCause();
}
}
- void parse(IntSupplier inputSource) throws EDIException {
+ public void parse(CharBuffer buffer) throws EDIException {
+ IntSupplier inputSource = () -> buffer.hasRemaining() ? buffer.get() : -1;
+ parse(inputSource, true);
+ }
+
+ public void signalElementDataCompleteEvent(int delimiter) throws EDIException {
+ CharacterClass clazz = characters.getClass(delimiter);
+ State dataCompleteState = State.transition(state, dialect, clazz);
+
+ switch (dataCompleteState) {
+ case SEGMENT_BEGIN:
+ case TRAILER_BEGIN:
+ openSegment();
+ nextEvent();
+ dataEventNotified = true;
+ break;
+ case COMPONENT_END:
+ handleComponent();
+ nextEvent();
+ dataEventNotified = true;
+ break;
+ case ELEMENT_END:
+ case TRAILER_ELEMENT_END:
+ handleElement();
+ nextEvent();
+ dataEventNotified = true;
+ break;
+ default:
+ throw new IllegalStateException(dataCompleteState.toString());
+ }
+ }
+
+ void parse(IntSupplier inputSource, boolean allowPartialStream) throws EDIException {
if (nextEvent()) {
return;
}
if (state == State.INVALID) {
// Unable to proceed once the state becomes invalid
- throw invalidStateError();
+ throw invalidStateError(previousInput);
}
+ int input = 0;
boolean eventsReady = false;
while (!eventsReady && (input = inputSource.getAsInt()) > -1) {
- location.incrementOffset(input);
+ eventsReady = processInputCharacter(input);
+ }
- CharacterClass clazz = characters.getClass(input);
- previous = state;
- state = State.transition(state, dialect, clazz);
- LOGGER.finer(() -> String.format("%s + (%s, '%s', %s) -> %s", previous, Dialect.getStandard(dialect), (char) input, clazz, state));
-
- switch (state) {
- case INITIAL:
- case TAG_SEARCH:
- case HEADER_EDIFACT_UNB_SEARCH:
- break;
- case HEADER_X12_I:
- case HEADER_X12_S:
- case HEADER_EDIFACT_N:
- case HEADER_EDIFACT_U:
- case HEADER_TRADACOMS_S:
- case HEADER_TRADACOMS_T:
- case TAG_1:
- case TAG_2:
- case TAG_3:
- case TRAILER_X12_I:
- case TRAILER_X12_E:
- case TRAILER_X12_A:
- case TRAILER_EDIFACT_U:
- case TRAILER_EDIFACT_N:
- case TRAILER_EDIFACT_Z:
- case TRAILER_TRADACOMS_E:
- case TRAILER_TRADACOMS_N:
- case TRAILER_TRADACOMS_D:
- case ELEMENT_DATA:
- case TRAILER_ELEMENT_DATA:
+ if (input < 0 && !allowPartialStream) {
+ throw error(EDIException.INCOMPLETE_STREAM);
+ }
+ }
+
+ boolean processInputCharacter(int input) throws EDIException {
+ boolean eventsReady = false;
+ location.incrementOffset(input);
+
+ CharacterClass clazz = characters.getClass(input);
+ previous = state;
+ previousInput = input;
+
+ state = State.transition(state, dialect, clazz);
+ LOGGER.finer(() -> String.format("%s + (%s, '%s', %s) -> %s", previous, Dialect.getStandard(dialect), (char) input, clazz, state));
+
+ switch (state) {
+ case INITIAL:
+ case TAG_SEARCH:
+ case HEADER_EDIFACT_UNB_SEARCH:
+ break;
+ case HEADER_X12_I:
+ case HEADER_X12_S:
+ case HEADER_EDIFACT_N:
+ case HEADER_EDIFACT_U:
+ case HEADER_TRADACOMS_S:
+ case HEADER_TRADACOMS_T:
+ case TAG_1:
+ case TAG_2:
+ case TAG_3:
+ case TRAILER_X12_I:
+ case TRAILER_X12_E:
+ case TRAILER_X12_A:
+ case TRAILER_EDIFACT_U:
+ case TRAILER_EDIFACT_N:
+ case TRAILER_EDIFACT_Z:
+ case TRAILER_TRADACOMS_E:
+ case TRAILER_TRADACOMS_N:
+ case TRAILER_TRADACOMS_D:
+ case ELEMENT_DATA:
+ case TRAILER_ELEMENT_DATA:
+ buffer.put((char) input);
+ break;
+ case ELEMENT_INVALID_DATA:
+ if (!characters.isIgnored(input)) {
buffer.put((char) input);
- break;
- case ELEMENT_INVALID_DATA:
- if (!characters.isIgnored(input)) {
- buffer.put((char) input);
- }
- break;
- case HEADER_EDIFACT_UNB_1: // U - When UNA is present
- case HEADER_EDIFACT_UNB_2: // N - When UNA is present
- case HEADER_EDIFACT_UNB_3: // B - When UNA is present
- handleStateHeaderTag(input);
- break;
- case HEADER_RELEASE:
- case DATA_RELEASE:
- // Skip this character - next character will be literal value
- break;
- case ELEMENT_DATA_BINARY:
- handleStateElementDataBinary();
- break;
- case INTERCHANGE_CANDIDATE:
- // ISA, UNA, or UNB was found
- handleStateInterchangeCandidate(input);
- break;
- case HEADER_DATA:
- case HEADER_INVALID_DATA:
- handleStateHeaderData(input);
- eventsReady = dialectConfirmed(State.TAG_SEARCH);
- break;
- case HEADER_SEGMENT_BEGIN:
- dialect.appendHeader(characters, (char) input);
- openSegment();
- eventsReady = dialectConfirmed(State.ELEMENT_END);
- break;
- case HEADER_ELEMENT_END:
- dialect.appendHeader(characters, (char) input);
- handleElement();
- eventsReady = dialectConfirmed(State.ELEMENT_END);
- break;
- case HEADER_COMPONENT_END:
- dialect.appendHeader(characters, (char) input);
- handleComponent();
- eventsReady = dialectConfirmed(State.COMPONENT_END);
- break;
- case SEGMENT_BEGIN:
- case TRAILER_BEGIN:
- openSegment();
- eventsReady = nextEvent();
- break;
- case SEGMENT_END:
- closeSegment();
- eventsReady = nextEvent();
- break;
- case SEGMENT_EMPTY:
- emptySegment();
- eventsReady = nextEvent();
- break;
- case COMPONENT_END:
- handleComponent();
- eventsReady = nextEvent();
- break;
- case ELEMENT_END:
- case TRAILER_ELEMENT_END:
- case ELEMENT_REPEAT:
- handleElement();
- eventsReady = nextEvent();
- break;
- case INTERCHANGE_END:
- closeInterchange();
- eventsReady = nextEvent();
- break;
- default:
- if (characters.isIgnored(input)) {
- state = previous;
- } else if (clazz != CharacterClass.INVALID) {
- throw invalidStateError();
- } else {
- throw error(EDIException.INVALID_CHARACTER);
- }
+ }
+ break;
+ case HEADER_EDIFACT_UNB_1: // U - When UNA is present
+ case HEADER_EDIFACT_UNB_2: // N - When UNA is present
+ case HEADER_EDIFACT_UNB_3: // B - When UNA is present
+ handleStateHeaderTag(input);
+ break;
+ case HEADER_RELEASE:
+ case DATA_RELEASE:
+ // Skip this character - next character will be literal value
+ break;
+ case ELEMENT_DATA_BINARY:
+ handleStateElementDataBinary();
+ break;
+ case INTERCHANGE_CANDIDATE:
+ // ISA, UNA, or UNB was found
+ handleStateInterchangeCandidate(input);
+ break;
+ case HEADER_DATA:
+ case HEADER_INVALID_DATA:
+ handleStateHeaderData(input);
+ eventsReady = dialectConfirmed(State.TAG_SEARCH);
+ break;
+ case HEADER_SEGMENT_BEGIN:
+ dialect.appendHeader(characters, (char) input);
+ openSegment();
+ eventsReady = dialectConfirmed(State.ELEMENT_END);
+ break;
+ case HEADER_SEGMENT_END:
+ dialect.appendHeader(characters, (char) input);
+ closeSegment();
+ eventsReady = dialectConfirmed(State.SEGMENT_END);
+ break;
+ case HEADER_ELEMENT_END:
+ dialect.appendHeader(characters, (char) input);
+ handleElement();
+ eventsReady = dialectConfirmed(State.ELEMENT_END);
+ break;
+ case HEADER_COMPONENT_END:
+ dialect.appendHeader(characters, (char) input);
+ handleComponent();
+ eventsReady = dialectConfirmed(State.COMPONENT_END);
+ break;
+ case SEGMENT_BEGIN:
+ case TRAILER_BEGIN:
+ openSegment();
+ eventsReady = nextEvent();
+ break;
+ case SEGMENT_END:
+ closeSegment();
+ eventsReady = nextEvent();
+ break;
+ case SEGMENT_EMPTY:
+ emptySegment();
+ eventsReady = nextEvent();
+ break;
+ case COMPONENT_END:
+ handleComponent();
+ eventsReady = nextEvent();
+ break;
+ case ELEMENT_END:
+ case TRAILER_ELEMENT_END:
+ case ELEMENT_REPEAT:
+ handleElement();
+ eventsReady = nextEvent();
+ break;
+ case INTERCHANGE_END:
+ closeInterchange();
+ eventsReady = nextEvent();
+ break;
+ default:
+ if (characters.isIgnored(input)) {
+ state = previous;
+ } else if (clazz != CharacterClass.INVALID) {
+ throw invalidStateError(input);
+ } else {
+ throw error(EDIException.INVALID_CHARACTER);
}
}
- if (input < 0) {
- throw error(EDIException.INCOMPLETE_STREAM);
- }
+ return eventsReady;
}
int readCharacterUnchecked() {
@@ -445,7 +509,7 @@ private boolean dialectConfirmed(State confirmed) throws EDIException {
return false;
}
- private EDIException invalidStateError() {
+ private EDIException invalidStateError(int input) {
StringBuilder message = new StringBuilder();
message.append(state);
message.append(" (previous: ");
@@ -476,10 +540,10 @@ private boolean nextEvent() {
int start = startQueue.remove();
int length = lengthQueue.remove();
eventsReady = event.execute(nextState, start, length);
- }
- if (events.isEmpty()) {
- buffer.clear();
+ if (events.isEmpty()) {
+ buffer.clear();
+ }
}
return eventsReady;
@@ -522,7 +586,9 @@ private void closeInterchange() throws EDIException {
}
private void openSegment() {
- modes.push(Mode.SEGMENT);
+ if (modes.peek() != Mode.SEGMENT) {
+ modes.push(Mode.SEGMENT);
+ }
enqueue(ssn, buffer.position());
}
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 0238d550..caa455fe 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
@@ -153,7 +153,7 @@ public String getReferenceCode() {
return current(StreamEvent::getReferenceCode, null);
}
- public Location getLocation() {
+ public StaEDIStreamLocation getLocation() {
return current(StreamEvent::getLocation, this.location);
}
@@ -388,6 +388,7 @@ public boolean elementData(CharSequence text, boolean fromStream) {
* and the composite begin/end events must be generated.
**/
final boolean componentReceivedAsSimple;
+ final List errors;
if (validator != null) {
derivedComposite = !compositeFromStream && validator.isComposite(dialect, location);
@@ -400,15 +401,17 @@ public boolean elementData(CharSequence text, boolean fromStream) {
valid = validator.validateElement(dialect, location, text, null);
typeReference = validator.getElementReference();
+ errors = validator.getElementErrors();
enqueueElementOccurrenceErrors(text, validator, valid);
} else {
- valid = true;
+ errors = Validator.validateCharacters(text);
+ valid = errors.isEmpty();
derivedComposite = false;
componentReceivedAsSimple = false;
typeReference = null;
}
- enqueueElementErrors(text, validator, valid);
+ enqueueElementErrors(text, errors, valid);
boolean eventsReady = true;
@@ -532,13 +535,11 @@ void enqueueElementOccurrenceErrors(CharSequence text, Validator validator, bool
}
}
- void enqueueElementErrors(CharSequence text, Validator validator, boolean valid) {
+ void enqueueElementErrors(CharSequence text, List errors, boolean valid) {
if (valid) {
return;
}
- List errors = validator.getElementErrors();
-
for (UsageError error : errors) {
enqueueEvent(error.getError().getCategory(),
error.getError(),
diff --git a/src/main/java/io/xlate/edi/internal/stream/tokenization/StreamEvent.java b/src/main/java/io/xlate/edi/internal/stream/tokenization/StreamEvent.java
index 812b7fb3..13050efc 100644
--- a/src/main/java/io/xlate/edi/internal/stream/tokenization/StreamEvent.java
+++ b/src/main/java/io/xlate/edi/internal/stream/tokenization/StreamEvent.java
@@ -70,7 +70,7 @@ public void setTypeReference(EDIReference typeReference) {
this.typeReference = typeReference;
}
- public Location getLocation() {
+ public StaEDIStreamLocation getLocation() {
return location;
}
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 f94a8b80..ff7523a1 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
@@ -45,18 +45,30 @@ void validate(Dialect dialect,
Set valueSet = element.getValueSet(dialect.getTransactionVersionString());
- if (!valueSet.isEmpty() && !valueSet.contains(value.toString())) {
- errors.add(EDIStreamValidationError.INVALID_CODE_VALUE);
+ if (valueSet.isEmpty() || valueSet.contains(value.toString())) {
+ if (valueSet.isEmpty()) {
+ // Only validate the characters if not explicitly listed in the set of allowed values
+ validateCharacters(value, length, errors);
+ }
} else {
- for (int i = 0; i < length; i++) {
- char character = value.charAt(i);
+ validateCharacters(value, length, errors);
+ errors.add(EDIStreamValidationError.INVALID_CODE_VALUE);
+ }
+ }
- if (!CharacterSet.isValid(character)) {
- errors.add(EDIStreamValidationError.INVALID_CHARACTER_DATA);
- break;
- }
+ static void validateCharacters(CharSequence value, int length, List errors) {
+ if (!validCharacters(value, length)) {
+ errors.add(EDIStreamValidationError.INVALID_CHARACTER_DATA);
+ }
+ }
+
+ static boolean validCharacters(CharSequence value, int length) {
+ for (int i = 0; i < length; i++) {
+ if (!CharacterSet.isValid(value.charAt(i))) {
+ return false;
}
}
+ return true;
}
@Override
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 e0ad7f89..272e4f0e 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
@@ -1263,6 +1263,13 @@ void validateElementValue(Dialect dialect, StaEDIStreamLocation position, UsageN
}
}
+ public static List validateCharacters(CharSequence value) {
+ if (AlphaNumericValidator.validCharacters(value, value.length())) {
+ return Collections.emptyList();
+ }
+ return Collections.singletonList(new UsageError(EDIStreamValidationError.INVALID_CHARACTER_DATA));
+ }
+
void validateControlValue(UsageNode loop, StaEDIStreamLocation position, CharSequence value, List errors) {
if (loop instanceof ControlUsageNode) {
((ControlUsageNode) loop).validateReference(position, value, errors);
diff --git a/src/main/java/io/xlate/edi/stream/EDIInputFactory.java b/src/main/java/io/xlate/edi/stream/EDIInputFactory.java
index f074c429..b1e996a1 100644
--- a/src/main/java/io/xlate/edi/stream/EDIInputFactory.java
+++ b/src/main/java/io/xlate/edi/stream/EDIInputFactory.java
@@ -24,6 +24,19 @@
public abstract class EDIInputFactory extends PropertySupport {
+ /**
+ * When set to false, control structures, segments, elements, and codes will
+ * not be validated unless a user-provided control schema has been set using
+ * {@link EDIStreamReader#setControlSchema(Schema)}.
+ *
+ * When set to true AND no user-provided control schema has been set, the
+ * reader will attempt to find a known control schema for the detected EDI
+ * dialect and version to be used for control structure validation.
+ *
+ * Default value: true
+ *
+ * @see EDIOutputFactory#EDI_VALIDATE_CONTROL_STRUCTURE
+ */
public static final String EDI_VALIDATE_CONTROL_STRUCTURE = "io.xlate.edi.stream.EDI_VALIDATE_CONTROL_STRUCTURE";
/**
diff --git a/src/main/java/io/xlate/edi/stream/EDIOutputFactory.java b/src/main/java/io/xlate/edi/stream/EDIOutputFactory.java
index b1b1635a..230de65c 100644
--- a/src/main/java/io/xlate/edi/stream/EDIOutputFactory.java
+++ b/src/main/java/io/xlate/edi/stream/EDIOutputFactory.java
@@ -19,6 +19,8 @@
import javax.xml.stream.XMLStreamWriter;
+import io.xlate.edi.schema.Schema;
+
public abstract class EDIOutputFactory extends PropertySupport {
/**
@@ -54,6 +56,23 @@ public abstract class EDIOutputFactory extends PropertySupport {
*/
public static final String FORMAT_ELEMENTS = "io.xlate.edi.stream.FORMAT_ELEMENTS";
+ /**
+ * When set to false, control structures, segments, elements, and codes will
+ * not be validated unless a user-provided control schema has been set using
+ * {@link EDIStreamWriter#setControlSchema(Schema)}.
+ *
+ * When set to true AND no user-provided control schema has been set, the
+ * writer will attempt to find a known control schema for the detected EDI
+ * dialect and version to be used for control structure validation.
+ *
+ * Default value: false
+ *
+ * @see EDIInputFactory#EDI_VALIDATE_CONTROL_STRUCTURE
+ *
+ * @since 2.0
+ */
+ public static final String EDI_VALIDATE_CONTROL_STRUCTURE = "io.xlate.edi.stream.EDI_VALIDATE_CONTROL_STRUCTURE";
+
/**
* 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 c1ae9172..d4ea71bb 100644
--- a/src/main/java/io/xlate/edi/stream/EDIStreamException.java
+++ b/src/main/java/io/xlate/edi/stream/EDIStreamException.java
@@ -21,6 +21,14 @@ public class EDIStreamException extends Exception {
protected final transient Location location;
+ protected static String buildMessage(String message, Location location) {
+ String locationString = location.toString();
+ if (message.contains(locationString)) {
+ return message;
+ }
+ return message + " " + locationString;
+ }
+
/**
* Construct an exception with the associated message.
*
@@ -58,7 +66,7 @@ public EDIStreamException(Throwable cause) {
* a nested error / exception
*/
public EDIStreamException(String message, Location location, Throwable cause) {
- super(message + " " + location.toString(), cause);
+ super(buildMessage(message, location), cause);
this.location = location;
}
@@ -72,7 +80,7 @@ public EDIStreamException(String message, Location location, Throwable cause) {
* the location of the error
*/
public EDIStreamException(String message, Location location) {
- super(message + " " + location.toString());
+ super(buildMessage(message, location));
this.location = location;
}
diff --git a/src/main/java/io/xlate/edi/stream/EDIValidationException.java b/src/main/java/io/xlate/edi/stream/EDIValidationException.java
index 7563ae29..dfce3048 100644
--- a/src/main/java/io/xlate/edi/stream/EDIValidationException.java
+++ b/src/main/java/io/xlate/edi/stream/EDIValidationException.java
@@ -16,7 +16,7 @@ public EDIValidationException(EDIStreamEvent event,
EDIStreamValidationError error,
Location location,
CharSequence data) {
- super("Encountered " + event + " [" + error + "]" + (location != null ? " " + location.toString() : ""));
+ super("Encountered " + event + " [" + error + "] for data={" + data + "} " + (location != null ? " " + location.toString() : ""));
this.event = event;
this.error = error;
this.location = location != null ? location.copy() : null;
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 c965b5d7..98606eee 100644
--- a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java
@@ -39,6 +39,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
@@ -92,9 +93,10 @@ private void writeHeader(EDIStreamWriter writer) throws EDIStreamException {
static void unconfirmedBufferEquals(String expected, EDIStreamWriter writer) {
StaEDIStreamWriter writerImpl = (StaEDIStreamWriter) writer;
- writerImpl.unconfirmedBuffer.mark();
- writerImpl.unconfirmedBuffer.flip();
- assertEquals(expected, writerImpl.unconfirmedBuffer.toString());
+ if (writerImpl.outputBuffer.position() > 0) {
+ writerImpl.outputBuffer.flip();
+ }
+ assertEquals(expected, writerImpl.outputBuffer.toString());
}
@Test
@@ -185,7 +187,6 @@ void testWriteStartSegment() throws EDIStreamException {
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
writer.startInterchange();
writer.writeStartSegment("ISA");
- writer.flush();
unconfirmedBufferEquals("ISA", writer);
}
@@ -207,8 +208,9 @@ void testWriteInvalidHeaderElement() throws EDIStreamException {
writer.writeElement("508121953");
writer.writeElement("0");
writer.writeElement("P");
- EDIStreamException thrown = assertThrows(EDIStreamException.class, () -> writer.writeElement(":"));
- assertEquals("Failed writing X12 header: Element delimiter '*' required in position 18 of X12 header but not found", thrown.getMessage());
+ writer.writeElement(":");
+ EDIStreamException thrown = assertThrows(EDIStreamException.class, () -> writer.writeEndSegment());
+ assertEquals("EDIE003 - Invalid processing state at offset 106; Element delimiter '*' required in position 18 of X12 header but not found", thrown.getMessage());
}
@Test
@@ -287,14 +289,27 @@ void testWriteStartElementIllegal() throws EDIStreamException {
@Test
void testWriteInvalidCharacter() throws EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
+ factory.setProperty(EDIOutputFactory.EDI_VALIDATE_CONTROL_STRUCTURE, true);
OutputStream stream = new ByteArrayOutputStream(4096);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
writer.startInterchange();
- writer.writeStartSegment("ISA");
- writer.writeStartElement();
- EDIStreamException thrown = assertThrows(EDIStreamException.class,
- () -> writer.writeElementData("\u0008\u0010"));
- assertEquals("Invalid character: 0x0008 in segment ISA at position 1, element 1", thrown.getMessage());
+ writeHeader(writer);
+ writer.writeStartSegment("GS");
+ EDIValidationException thrown = assertThrows(EDIValidationException.class,
+ () -> writer.writeElement("\u0008\u0010"));
+
+ assertEquals(EDIStreamEvent.ELEMENT_DATA_ERROR, thrown.getEvent());
+ assertEquals(EDIStreamValidationError.INVALID_CHARACTER_DATA, thrown.getError());
+ assertEquals("\u0008\u0010", thrown.getData().toString());
+ assertEquals(2, thrown.getLocation().getSegmentPosition());
+ assertEquals(1, thrown.getLocation().getElementPosition());
+
+ assertNotNull(thrown = thrown.getNextException());
+ assertEquals(EDIStreamEvent.ELEMENT_DATA_ERROR, thrown.getEvent());
+ assertEquals(EDIStreamValidationError.INVALID_CODE_VALUE, thrown.getError());
+ assertEquals("\u0008\u0010", thrown.getData().toString());
+ assertEquals(2, thrown.getLocation().getSegmentPosition());
+ assertEquals(1, thrown.getLocation().getElementPosition());
}
@Test
@@ -309,9 +324,12 @@ void testWriteInvalidCharacterRepeatedComposite() throws EDIStreamException {
writer.writeRepeatElement(); // starts new element
writer.writeComponent("BAR2");
writer.writeComponent("BAR3");
- EDIStreamException thrown = assertThrows(EDIStreamException.class,
+ EDIValidationException thrown = assertThrows(EDIValidationException.class,
() -> writer.writeComponent("\u0008\u0010"));
- assertEquals("Invalid character: 0x0008 in segment FOO at position 2, element 1 (occurrence 2), component 3", thrown.getMessage());
+
+ assertEquals(EDIStreamEvent.ELEMENT_DATA_ERROR, thrown.getEvent());
+ assertEquals(EDIStreamValidationError.INVALID_CHARACTER_DATA, thrown.getError());
+ assertEquals("\u0008\u0010", thrown.getData().toString());
Location l = thrown.getLocation();
assertEquals("FOO", l.getSegmentTag());
assertEquals(2, l.getSegmentPosition());
@@ -328,14 +346,15 @@ void testWriteInvalidSegmentTag() throws EDIStreamException {
writer.startInterchange();
writeHeader(writer);
writer.writeStartSegment("G");
- EDIStreamException thrown = assertThrows(EDIStreamException.class,
- () -> writer.writeElement("FOO"));
- assertEquals("Invalid state: INVALID; output 0x002A", thrown.getMessage());
+ // Illegal transition from segment tag position 1 to element delimiter
+ assertThrows(EDIStreamException.class, () -> writer.writeElement("FOO"));
}
@Test
void testWriteStartElementBinary() throws IllegalStateException, EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
+ factory.setProperty(EDIOutputFactory.EDI_VALIDATE_CONTROL_STRUCTURE, true);
+ ListErrorReporter reporter = setErrorReporter(factory);
ByteArrayOutputStream stream = new ByteArrayOutputStream(4096);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
writer.startInterchange();
@@ -346,6 +365,8 @@ void testWriteStartElementBinary() throws IllegalStateException, EDIStreamExcept
writer.writeStartElementBinary().writeEndSegment();
writer.flush();
assertEquals("BIN*~", stream.toString());
+ assertEquals(1, reporter.errors.size());
+ assertTrue(reporter.hasError(0, EDIStreamValidationError.SEGMENT_NOT_IN_DEFINED_TRANSACTION_SET, "BIN"));
}
@Test
@@ -374,6 +395,8 @@ void testWriteBinaryDataIllegal() throws IllegalStateException, EDIStreamExcepti
@Test
void testStartComponentIllegalInElementBinary() throws IllegalStateException, EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
+ factory.setProperty(EDIOutputFactory.EDI_VALIDATE_CONTROL_STRUCTURE, true);
+ ListErrorReporter reporter = setErrorReporter(factory);
ByteArrayOutputStream stream = new ByteArrayOutputStream(4096);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
writer.startInterchange();
@@ -383,6 +406,7 @@ void testStartComponentIllegalInElementBinary() throws IllegalStateException, ED
writer.writeStartSegment("BIN");
writer.writeStartElementBinary();
assertThrows(IllegalStateException.class, () -> writer.startComponent());
+ assertTrue(reporter.hasError(0, EDIStreamValidationError.SEGMENT_NOT_IN_DEFINED_TRANSACTION_SET, "BIN"));
}
@Test
@@ -437,6 +461,8 @@ void testComponentIllegal() throws IllegalStateException, EDIStreamException {
@Test
void testWriteRepeatElement() throws EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
+ factory.setProperty(EDIOutputFactory.EDI_VALIDATE_CONTROL_STRUCTURE, true);
+ ListErrorReporter reporter = setErrorReporter(factory);
ByteArrayOutputStream stream = new ByteArrayOutputStream(4096);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
writer.startInterchange();
@@ -451,6 +477,7 @@ void testWriteRepeatElement() throws EDIStreamException {
.writeEndSegment();
writer.flush();
assertEquals("SEG*R1^R2~", stream.toString());
+ assertTrue(reporter.hasError(0, EDIStreamValidationError.SEGMENT_NOT_IN_DEFINED_TRANSACTION_SET, "SEG"));
}
@Test
@@ -575,8 +602,6 @@ void testWriteElementDataCharSequence() throws EDIStreamException {
writer.writeStartElement();
writer.writeElementData("TEST-ELEMENT");
assertThrows(EDIStreamException.class, () -> writer.writeEndSegment());
- writer.flush();
- unconfirmedBufferEquals("ISA*TEST-ELEMENT~", writer);
}
@Test
@@ -635,6 +660,8 @@ void testWriteElementDataCharArrayIllegal() throws EDIStreamException {
@Test
void testWriteBinaryDataInputStream() throws EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
+ factory.setProperty(EDIOutputFactory.EDI_VALIDATE_CONTROL_STRUCTURE, true);
+ ListErrorReporter reporter = setErrorReporter(factory);
ByteArrayOutputStream stream = new ByteArrayOutputStream(4096);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
byte[] binary = { '\n', 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, '\t' };
@@ -652,12 +679,15 @@ void testWriteBinaryDataInputStream() throws EDIStreamException {
writer.endElement();
writer.writeEndSegment();
writer.flush();
+ assertTrue(reporter.hasError(0, EDIStreamValidationError.SEGMENT_NOT_IN_DEFINED_TRANSACTION_SET, "BIN"));
assertEquals("BIN*8*\n\u0000\u0001\u0002\u0003\u0004\u0005\t~", stream.toString());
}
@Test
void testWriteBinaryDataInputStreamIOException() throws Exception {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
+ factory.setProperty(EDIOutputFactory.EDI_VALIDATE_CONTROL_STRUCTURE, true);
+ ListErrorReporter reporter = setErrorReporter(factory);
ByteArrayOutputStream stream = new ByteArrayOutputStream(4096);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
InputStream binaryStream = Mockito.mock(InputStream.class);
@@ -676,11 +706,14 @@ void testWriteBinaryDataInputStreamIOException() throws Exception {
assertEquals("Exception writing binary element data in segment BIN at position 2, element 2",
thrown.getMessage());
assertSame(ioException, thrown.getCause());
+ assertTrue(reporter.hasError(0, EDIStreamValidationError.SEGMENT_NOT_IN_DEFINED_TRANSACTION_SET, "BIN"));
}
@Test
void testWriteBinaryDataByteArray() throws EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
+ factory.setProperty(EDIOutputFactory.EDI_VALIDATE_CONTROL_STRUCTURE, true);
+ ListErrorReporter reporter = setErrorReporter(factory);
ByteArrayOutputStream stream = new ByteArrayOutputStream(4096);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
byte[] binary = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A' };
@@ -698,13 +731,17 @@ void testWriteBinaryDataByteArray() throws EDIStreamException {
writer.writeEndSegment();
writer.flush();
assertEquals("BIN*11*0123456789A~", stream.toString());
+ assertTrue(reporter.hasError(0, EDIStreamValidationError.SEGMENT_NOT_IN_DEFINED_TRANSACTION_SET, "BIN"));
}
@Test
void testWriteBinaryDataByteBuffer() throws EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
+ factory.setProperty(EDIOutputFactory.EDI_VALIDATE_CONTROL_STRUCTURE, true);
+ ListErrorReporter reporter = setErrorReporter(factory);
ByteArrayOutputStream stream = new ByteArrayOutputStream(4096);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
+
byte[] binary = { 'B', 'U', 'S', 'T', 'M', 'Y', 'B', 'U', 'F', 'F', 'E', 'R', 'S', '\n' };
ByteBuffer buffer = ByteBuffer.wrap(binary);
writer.startInterchange();
@@ -721,6 +758,8 @@ void testWriteBinaryDataByteBuffer() throws EDIStreamException {
writer.writeEndSegment();
writer.flush();
assertEquals("BIN*14*BUSTMYBUFFERS\n~", stream.toString());
+ assertEquals(1, reporter.errors.size());
+ assertTrue(reporter.hasError(0, EDIStreamValidationError.SEGMENT_NOT_IN_DEFINED_TRANSACTION_SET, "BIN"));
}
@ParameterizedTest
@@ -998,7 +1037,9 @@ public int read() throws IOException {
EDIOutputFactory outputFactory = EDIOutputFactory.newFactory();
outputFactory.setProperty(EDIOutputFactory.PRETTY_PRINT, true);
+ outputFactory.setProperty(EDIOutputFactory.EDI_VALIDATE_CONTROL_STRUCTURE, true);
outputFactory.setProperty(Delimiters.REPETITION, '*');
+ ListErrorReporter reporter = setErrorReporter(outputFactory);
ByteArrayOutputStream result = new ByteArrayOutputStream(16384);
EDIStreamWriter writer = null;
@@ -1080,6 +1121,7 @@ public int read() throws IOException {
}
assertEquals(normalizeLines(expected.toString().trim()), normalizeLines(result.toString().trim()));
+ assertTrue(reporter.hasError(0, EDIStreamValidationError.INVALID_CODE_VALUE, "IATA"));
}
@Test
@@ -1198,12 +1240,14 @@ void testValidatedSegmentTagsExceptionThrown() throws EDISchemaException, EDIStr
writer.startInterchange();
writeHeader(writer);
- EDIValidationException e = assertThrows(EDIValidationException.class, () -> writer.writeStartSegment("ST"));
+ writer.writeStartSegment("ST");
+ EDIValidationException e = assertThrows(EDIValidationException.class, () -> writer.writeStartElement());
assertEquals(EDIStreamEvent.SEGMENT_ERROR, e.getEvent());
assertEquals(EDIStreamValidationError.LOOP_OCCURS_OVER_MAXIMUM_TIMES, e.getError());
assertEquals("ST", e.getData().toString());
assertEquals("ST", e.getLocation().getSegmentTag());
assertEquals(2, e.getLocation().getSegmentPosition());
+ assertEquals(-1, e.getLocation().getElementPosition());
}
@Test
@@ -1223,12 +1267,15 @@ void testValidatedSegmentTagsReporterInvoked() throws EDISchemaException, EDIStr
writer.startInterchange();
writeHeader(writer);
- writer.writeStartSegment("ST");
+ writer.writeStartSegment("ST")
+ .writeElement("000")
+ .writeElement("0001")
+ .writeEndSegment();
assertEquals(5, actual.size());
assertEquals(EDIStreamEvent.SEGMENT_ERROR, actual.get(0));
assertEquals(EDIStreamValidationError.LOOP_OCCURS_OVER_MAXIMUM_TIMES, actual.get(1));
assertEquals("in segment ST at position 2", actual.get(2));
- assertEquals("ST", actual.get(3));
+ assertEquals("ST", actual.get(3).toString());
assertEquals("TRANSACTION", ((EDIReference) actual.get(4)).getReferencedType().getCode());
}
@@ -1240,19 +1287,21 @@ void testElementValidationReporterInvoked() throws EDISchemaException, EDIStream
};
EDIOutputFactory outputFactory = EDIOutputFactory.newFactory();
outputFactory.setProperty(EDIOutputFactory.PRETTY_PRINT, true);
+ outputFactory.setProperty(EDIOutputFactory.EDI_VALIDATE_CONTROL_STRUCTURE, true);
outputFactory.setErrorReporter(reporter);
assertSame(reporter, outputFactory.getErrorReporter());
ByteArrayOutputStream result = new ByteArrayOutputStream(16384);
EDIStreamWriter writer = outputFactory.createEDIStreamWriter(result);
- Schema control = SchemaUtils.getControlSchema("X12", new String[] { "00501" });
- writer.setControlSchema(control);
+ //Schema control = SchemaUtils.getControlSchema("X12", new String[] { "00501" });
+ //writer.setControlSchema(control);
writer.startInterchange();
writeHeader(writer);
writer.writeStartSegment("GS");
writer.writeElement("AAA");
+ writer.writeElement("SENDERID");
assertEquals(2, actual.size());
List