Skip to content

Commit

Permalink
Allow implementation nodes to occur/reoccur in any order in the input
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeEdgar committed Nov 28, 2021
1 parent 263c837 commit 97f39a3
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 131 deletions.
Expand Up @@ -564,72 +564,14 @@ private void enqueueEvent(EDIStreamEvent event,
Location location) {

StreamEvent target = getPooledEvent();
EDIStreamEvent associatedEvent = eventQueue.isEmpty() ? null : getAssociatedEvent(error);

if (eventExists(associatedEvent)) {
/*
* Ensure segment errors occur before other event types
* when the array has other events already present.
*/
int offset = eventQueue.size();
boolean complete = false;

while (!complete) {
StreamEvent enqueuedEvent = eventQueue.get(offset - 1);

if (enqueuedEvent.type == associatedEvent) {
complete = true;
} else {
if (eventQueue.size() == offset) {
eventQueue.add(offset, enqueuedEvent);
} else {
eventQueue.set(offset, enqueuedEvent);
}
offset--;
}
}

eventQueue.set(offset, target);
} else {
eventQueue.add(target);
}

target.type = event;
target.errorType = error;
target.setData(data);
target.setTypeReference(typeReference);
target.setLocation(location);
}

private boolean eventExists(EDIStreamEvent associatedEvent) {
int offset = eventQueue.size();

while (associatedEvent != null && offset > 0) {
if (eventQueue.get(offset - 1).type == associatedEvent) {
return true;
}
offset--;
}

return false;
eventQueue.add(target);
}

private static EDIStreamEvent getAssociatedEvent(EDIStreamValidationError error) {
final EDIStreamEvent event;

switch (error) {
case IMPLEMENTATION_LOOP_OCCURS_UNDER_MINIMUM_TIMES:
event = EDIStreamEvent.END_LOOP;
break;
case MANDATORY_SEGMENT_MISSING:
case IMPLEMENTATION_SEGMENT_BELOW_MINIMUM_USE:
event = null;
break;
default:
event = null;
break;
}

return event;
}
}
Expand Up @@ -18,6 +18,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import io.xlate.edi.internal.stream.tokenization.Dialect;
Expand All @@ -43,10 +44,7 @@ class UsageNode {
private int usageCount;

UsageNode(UsageNode parent, int depth, EDIReference link, int siblingIndex) {
if (link == null) {
throw new NullPointerException();
}

Objects.requireNonNull(link, "link");
this.parent = parent;
this.depth = depth;
this.link = link;
Expand All @@ -67,14 +65,21 @@ public static boolean hasMinimumUsage(String version, UsageNode node) {
return node == null || node.hasMinimumUsage(version);
}

public static UsageNode getParent(UsageNode node) {
return node != null ? node.getParent() : null;
}

public static UsageNode getFirstChild(UsageNode node) {
return node != null ? node.getFirstChild() : null;
}

public UsageNode getFirstSiblingSameType() {
EDIType type = getReferencedType();
UsageNode sibling = getFirstSibling();

while (!type.equals(sibling.getReferencedType())) {
sibling = sibling.getNextSibling();
}

return sibling;
}

public static void resetChildren(UsageNode... nodes) {
for (UsageNode node : nodes) {
if (node != null) {
Expand Down Expand Up @@ -228,14 +233,17 @@ public UsageNode getFirstChild() {
return getChild(0);
}

UsageNode getChildById(CharSequence id) {
return children.stream()
.filter(c -> c != null && c.getId().contentEquals(id))
.findFirst()
.orElse(null);
private UsageNode getChildById(CharSequence id) {
for (UsageNode child : children) {
if (child.getId().contentEquals(id)) {
return child;
}
}
return null;
}

UsageNode getSiblingById(CharSequence id) {
return parent != null ? parent.getChildById(id) : null;
}

}
Expand Up @@ -120,10 +120,10 @@ void next(UsageNode nextImpl) {
}

void nagivateUp(int limit) {
standard = UsageNode.getParent(standard);
standard = standard.getParent();

if (impl != null && impl.getDepth() > limit) {
impl = UsageNode.getParent(impl);
impl = impl.getParent().getFirstSiblingSameType();
}
}

Expand Down Expand Up @@ -439,7 +439,7 @@ public void validateSegment(ValidationEventHandler handler, CharSequence tag) {
*/
checkMinimumUsage(cursor.standard);

UsageNode nextImpl = checkMinimumImplUsage(cursor.impl, cursor.standard);
UsageNode nextImpl = getNextImplementationNode(cursor.impl, cursor.standard.getReferencedType());

if (cursor.hasNextSibling()) {
// Advance to the next segment in the loop
Expand All @@ -461,16 +461,6 @@ public void validateSegment(ValidationEventHandler handler, CharSequence tag) {
handleMissingMandatory(handler);
}

UsageNode checkMinimumImplUsage(UsageNode nextImpl, UsageNode current) {
while (nextImpl != null && nextImpl.getReferencedType().equals(current.getReferencedType())) {
// Advance past multiple implementations of the 'current' standard node
checkMinimumUsage(nextImpl);
nextImpl = nextImpl.getNextSibling();
}

return nextImpl;
}

boolean handleNode(CharSequence tag, UsageNode current, UsageNode currentImpl, int startDepth, ValidationEventHandler handler) {
final boolean handled;

Expand Down Expand Up @@ -589,6 +579,17 @@ void checkMinimumUsage(UsageNode node) {
}
}

UsageNode getNextImplementationNode(UsageNode implNode, EDIType type) {
while (implNode != null && implNode.getReferencedType().equals(type)) {
// Advance past multiple implementations of the 'current' standard node
checkMinimumUsage(implNode);
implNode = implNode.getNextSibling();
}

// `implNode` will be an implementation of the type following `type`
return implNode;
}

boolean handleLoop(CharSequence tag, UsageNode current, UsageNode currentImpl, int startDepth, ValidationEventHandler handler) {
if (!current.getFirstChild().getId().contentEquals(tag)) {
return false;
Expand Down Expand Up @@ -781,7 +782,6 @@ public boolean selectImplementation(Deque<StreamEvent> eventQueue, ValidationEve
}

void handleImplementationSelected(UsageNode candidate, UsageNode implSeg, ValidationEventHandler handler) {
checkMinimumImplUsage(implNode, candidate, handler);
implSegmentCandidates.clear();
implNode = implSeg;
implSegmentSelected = true;
Expand Down Expand Up @@ -820,14 +820,6 @@ void handleImplementationSelected(UsageNode candidate, UsageNode implSeg, Valida
}
}

void checkMinimumImplUsage(UsageNode sibling, UsageNode selected, ValidationEventHandler handler) {
while (sibling != null && sibling != selected) {
checkMinimumUsage(sibling);
sibling = sibling.getNextSibling();
}
handleMissingMandatory(handler);
}

/**
* Validate any implementation elements previously skipped while searching
* for the loop discriminator element. Validation of enumerated values specified
Expand Down
Expand Up @@ -23,6 +23,9 @@
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.jupiter.api.Test;

Expand All @@ -37,6 +40,8 @@
import io.xlate.edi.stream.EDIStreamFilter;
import io.xlate.edi.stream.EDIStreamReader;
import io.xlate.edi.stream.EDIStreamValidationError;
import io.xlate.edi.test.StaEDITestEvent;
import io.xlate.edi.test.StaEDITestUtil;

@SuppressWarnings("resource")
class SegmentValidationTest {
Expand Down Expand Up @@ -444,7 +449,6 @@ public boolean accept(EDIStreamReader reader) {
assertEvent(reader, EDIStreamEvent.SEGMENT_ERROR, EDIStreamValidationError.IMPLEMENTATION_SEGMENT_BELOW_MINIMUM_USE, "S13");
assertEvent(reader, EDIStreamEvent.END_LOOP, "0000A");


// Loop B - Occurrence 1
assertEvent(reader, EDIStreamEvent.START_LOOP, "0000B");
assertEvent(reader, EDIStreamEvent.SEGMENT_ERROR, EDIStreamValidationError.SEGMENT_EXCEEDS_MAXIMUM_USE, "S12");
Expand All @@ -463,15 +467,15 @@ public boolean accept(EDIStreamReader reader) {
assertEvent(reader, EDIStreamEvent.START_LOOP, "0000C");
assertEvent(reader, EDIStreamEvent.END_LOOP, "0000C");

assertEvent(reader, EDIStreamEvent.SEGMENT_ERROR, EDIStreamValidationError.IMPLEMENTATION_LOOP_OCCURS_UNDER_MINIMUM_TIMES, "S11");

// Loop D - Occurrence 1
assertEvent(reader, EDIStreamEvent.START_LOOP, "0000D");

// Standard Loop L0000 may only occur 5 times
assertEvent(reader, EDIStreamEvent.SEGMENT_ERROR, EDIStreamValidationError.LOOP_OCCURS_OVER_MAXIMUM_TIMES, "S11", "L0000");
assertEvent(reader, EDIStreamEvent.END_LOOP, "0000D");

assertEvent(reader, EDIStreamEvent.SEGMENT_ERROR, EDIStreamValidationError.IMPLEMENTATION_LOOP_OCCURS_UNDER_MINIMUM_TIMES, "S11");

// Loop L0001 has minOccurs=1 in standard (not used in implementation, invalid configuration)
assertEvent(reader, EDIStreamEvent.SEGMENT_ERROR, EDIStreamValidationError.MANDATORY_SEGMENT_MISSING, "S20");

Expand Down Expand Up @@ -612,48 +616,48 @@ void testImplementation_Only_BHT_HL_Valid() throws EDISchemaException, EDIStream
InputStream stream = getClass().getResourceAsStream("/x12/sample837-small.edi");

EDIStreamReader reader = factory.createEDIStreamReader(stream);
reader = factory.createFilteredReader(reader, new EDIStreamFilter() {
@Override
public boolean accept(EDIStreamReader reader) {
switch (reader.getEventType()) {
case START_TRANSACTION:
case SEGMENT_ERROR:
case START_LOOP:
case END_LOOP:
return true;
default:
return false;
}
}
});
reader = StaEDITestUtil.filterEvents(
factory,
reader,
EDIStreamEvent.START_TRANSACTION,
EDIStreamEvent.SEGMENT_ERROR,
EDIStreamEvent.START_LOOP,
EDIStreamEvent.END_LOOP);

assertEvent(reader, EDIStreamEvent.START_TRANSACTION);
SchemaFactory schemaFactory = SchemaFactory.newFactory();
URL schemaLocation = getClass().getResource("/x12/005010X222/837.xml");
Schema schema = schemaFactory.createSchema(schemaLocation);
reader.setTransactionSchema(schema);

// Occurrence 1
assertEvent(reader, EDIStreamEvent.START_LOOP, "L0001");
assertEvent(reader, EDIStreamEvent.SEGMENT_ERROR, EDIStreamValidationError.IMPLEMENTATION_UNUSED_SEGMENT_PRESENT, "NM1");
assertEvent(reader, EDIStreamEvent.SEGMENT_ERROR, EDIStreamValidationError.IMPLEMENTATION_UNUSED_SEGMENT_PRESENT, "PER");
assertEvent(reader, EDIStreamEvent.END_LOOP, "L0001");

// Occurrence 2
assertEvent(reader, EDIStreamEvent.START_LOOP, "L0001");
assertEvent(reader, EDIStreamEvent.SEGMENT_ERROR, EDIStreamValidationError.IMPLEMENTATION_UNUSED_SEGMENT_PRESENT, "NM1");
assertEvent(reader, EDIStreamEvent.END_LOOP, "L0001");

// Loop 2010A
assertEvent(reader, EDIStreamEvent.START_LOOP, "2010A");
assertEvent(reader, EDIStreamEvent.SEGMENT_ERROR, EDIStreamValidationError.IMPLEMENTATION_UNUSED_SEGMENT_PRESENT, "PRV");
assertEvent(reader, EDIStreamEvent.END_LOOP, "2010A");

assertEvent(reader, EDIStreamEvent.START_TRANSACTION);
// Loop 2010A
assertEvent(reader, EDIStreamEvent.START_LOOP, "2010A");
assertEvent(reader, EDIStreamEvent.END_LOOP, "2010A");
List<StaEDITestEvent> expected = Arrays.asList(
StaEDITestEvent.forEvent(EDIStreamEvent.START_TRANSACTION, "TRANSACTION", "TRANSACTION"),
// Occurrence 1
StaEDITestEvent.forEvent(EDIStreamEvent.START_LOOP, "L0001", "L0001"),
StaEDITestEvent.forError(EDIStreamValidationError.IMPLEMENTATION_UNUSED_SEGMENT_PRESENT, "NM1", "NM1"),
StaEDITestEvent.forError(EDIStreamValidationError.IMPLEMENTATION_UNUSED_SEGMENT_PRESENT, "PER", "PER"),
StaEDITestEvent.forEvent(EDIStreamEvent.END_LOOP, "L0001", "L0001"),
// Occurrence 2
StaEDITestEvent.forEvent(EDIStreamEvent.START_LOOP, "L0001", "L0001"),
StaEDITestEvent.forError(EDIStreamValidationError.IMPLEMENTATION_UNUSED_SEGMENT_PRESENT, "NM1", "NM1"),
StaEDITestEvent.forEvent(EDIStreamEvent.END_LOOP, "L0001", "L0001"),
// Loop 2010A
StaEDITestEvent.forEvent(EDIStreamEvent.START_LOOP, "L0002", "2010A"),
StaEDITestEvent.forError(EDIStreamValidationError.IMPLEMENTATION_UNUSED_SEGMENT_PRESENT, "PRV", "PRV"),
StaEDITestEvent.forEvent(EDIStreamEvent.END_LOOP, "L0002", "2010A"),
StaEDITestEvent.forEvent(EDIStreamEvent.START_TRANSACTION, "TRANSACTION", "TRANSACTION"),
// Loop 2010A
StaEDITestEvent.forEvent(EDIStreamEvent.START_LOOP, "L0002", "2010A"),
StaEDITestEvent.forEvent(EDIStreamEvent.END_LOOP, "L0002", "2010A"));

List<StaEDITestEvent> events = new ArrayList<>();
events.add(StaEDITestEvent.from(reader, false));

while (reader.hasNext()) {
events.add(StaEDITestEvent.from(reader, false));
}

assertEquals(expected, events);
assertTrue(!reader.hasNext(), "Unexpected events exist");
}
}

0 comments on commit 97f39a3

Please sign in to comment.