Skip to content

Commit

Permalink
[WIP] Push writer output through lexer and proxy handler for validation
Browse files Browse the repository at this point in the history
Fixes xlate#283
Fixes xlate#286

Signed-off-by: Michael Edgar <[email protected]>
  • Loading branch information
MikeEdgar committed Apr 30, 2023
1 parent 5aa9099 commit 6ef3f2d
Show file tree
Hide file tree
Showing 15 changed files with 983 additions and 608 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
1,011 changes: 589 additions & 422 deletions src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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");
}
Expand All @@ -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) {
Expand Down
288 changes: 169 additions & 119 deletions src/main/java/io/xlate/edi/internal/stream/tokenization/Lexer.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ private enum Mode {
}

private final Deque<Mode> 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);
Expand Down Expand Up @@ -117,6 +118,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);
};
Expand All @@ -127,6 +132,10 @@ public Dialect getDialect() {
return dialect;
}

public CharacterSet getCharacterSet() {
return characters;
}

public void invalidate() {
if (state != State.INVALID) {
previous = state;
Expand Down Expand Up @@ -171,141 +180,182 @@ 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 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() {
Expand Down Expand Up @@ -445,7 +495,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: ");
Expand Down Expand Up @@ -476,10 +526,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;
Expand Down
Loading

0 comments on commit 6ef3f2d

Please sign in to comment.