diff --git a/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamReader.java b/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamReader.java index 7ea912a7..e3d0576c 100644 --- a/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamReader.java +++ b/src/main/java/io/xlate/edi/internal/stream/StaEDIStreamReader.java @@ -72,6 +72,37 @@ public StaEDIStreamReader( this.lexer = new Lexer(stream, charset, proxy, location, ignoreExtraneousCharacters()); } + @Override + public String toString() { + StringBuilder buffer = new StringBuilder("StaEDIStreamReader("); + + buffer.append("complete=").append(Boolean.valueOf(complete)); + buffer.append(", ").append("closed=").append(Boolean.valueOf(closed)); + + if (!closed) { + EDIStreamEvent event = getEventType(); + buffer.append(", ").append("event=").append(getEventType()); + + switch (event) { + case SEGMENT_ERROR: + case ELEMENT_DATA_ERROR: + case ELEMENT_OCCURRENCE_ERROR: + buffer.append(", ").append("error=").append(getErrorType()); + buffer.append(", ").append("referenceCode=").append(getReferenceCode()); + break; + case ELEMENT_DATA: + buffer.append(", ").append("text=").append(getText()); + break; + default: + break; + } + + buffer.append(", ").append("location=").append(location); + } + + return buffer.append(')').toString(); + } + private void ensureOpen() { if (closed) { throw new IllegalStateException("Reader is closed"); 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 da16e570..4d47b4ea 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 @@ -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; - } } diff --git a/src/main/java/io/xlate/edi/internal/stream/validation/UsageNode.java b/src/main/java/io/xlate/edi/internal/stream/validation/UsageNode.java index 46a3eb7a..78e84afa 100644 --- a/src/main/java/io/xlate/edi/internal/stream/validation/UsageNode.java +++ b/src/main/java/io/xlate/edi/internal/stream/validation/UsageNode.java @@ -67,14 +67,25 @@ 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() { + if (parent == null) { + return this; + } + + 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) { @@ -228,14 +239,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; } + } 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 281b36ef..b05a4249 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 @@ -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(); } } @@ -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 @@ -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; @@ -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; @@ -781,7 +782,6 @@ public boolean selectImplementation(Deque eventQueue, ValidationEve } void handleImplementationSelected(UsageNode candidate, UsageNode implSeg, ValidationEventHandler handler) { - checkMinimumImplUsage(implNode, candidate, handler); implSegmentCandidates.clear(); implNode = implSeg; implSegmentSelected = true; @@ -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 diff --git a/src/test/java/io/xlate/edi/internal/stream/SegmentValidationTest.java b/src/test/java/io/xlate/edi/internal/stream/SegmentValidationTest.java index 74c22516..e8a87906 100644 --- a/src/test/java/io/xlate/edi/internal/stream/SegmentValidationTest.java +++ b/src/test/java/io/xlate/edi/internal/stream/SegmentValidationTest.java @@ -444,7 +444,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"); @@ -463,8 +462,6 @@ 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"); @@ -472,6 +469,8 @@ public boolean accept(EDIStreamReader reader) { 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");