Skip to content

Commit

Permalink
Merge pull request #230 from xlate/229_imple_validation_bugs
Browse files Browse the repository at this point in the history
Implementation validation fixes
  • Loading branch information
MikeEdgar committed Dec 12, 2021
2 parents 6fe4c15 + 0bb3788 commit 248026d
Show file tree
Hide file tree
Showing 13 changed files with 358 additions and 65 deletions.
118 changes: 63 additions & 55 deletions src/main/java/io/xlate/edi/internal/schema/SchemaReaderV3.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ void setReferences(Map<String, EDIType> types) {
super.setReferences(types);

StreamSupport.stream(Spliterators.spliteratorUnknownSize(implementedTypes.descendingIterator(),
Spliterator.ORDERED),
false)
Spliterator.ORDERED),
false)
.filter(type -> type.getType() != Type.ELEMENT)
.map(BaseComplexImpl.class::cast)
.forEach(type -> setReferences(type, types));
Expand Down Expand Up @@ -191,6 +191,10 @@ EDITypeImplementation getDefaultImplementation(EDIReference standardReference, i
return new ElementImpl(standardReference, position);
case COMPOSITE:
return new CompositeImpl(standardReference, position, getDefaultSequence(((EDIComplexType) std).getReferences()));
case SEGMENT:
return new SegmentImpl(standardReference, getDefaultSequence(((EDIComplexType) std).getReferences()));
case LOOP:
return new LoopImpl(standardReference, getDefaultSequence(((EDIComplexType) std).getReferences()));
default:
throw schemaException("Implementation of " + std.getId() + " must not be empty");
}
Expand All @@ -213,36 +217,37 @@ LoopImplementation readLoopImplementation(XMLStreamReader reader, QName complexT
List<EDITypeImplementation> sequence = new ArrayList<>();
String id;
String typeId;
int minOccurs = 0;
int maxOccurs = 0;
EDIElementPosition discriminatorPos = null;
String title = null;
int minOccurs;
int maxOccurs;
EDIElementPosition discriminatorPos;
String title;

if (transactionLoop) {
id = StaEDISchema.IMPLEMENTATION_ID;
typeId = null;
minOccurs = 0;
maxOccurs = 0;
discriminatorPos = null;
title = null;
} else {
id = parseAttribute(reader, "code", String::valueOf);
typeId = parseAttribute(reader, "type", String::valueOf);
minOccurs = parseAttribute(reader, ATTR_MIN_OCCURS, Integer::parseInt, -1);
maxOccurs = parseAttribute(reader, ATTR_MAX_OCCURS, Integer::parseInt, -1);
discriminatorPos = parseElementPosition(reader, ATTR_DISCRIMINATOR);
discriminatorPos = parseElementPosition(reader, ATTR_DISCRIMINATOR);
title = parseAttribute(reader, ATTR_TITLE, String::valueOf, null);
}

String descr = readDescription(reader);
readSequence(reader, e -> readLoopSequenceEntry(e, sequence));
nextTag(reader, "reading to end of " + complexType);
requireElement(complexType, reader);

Discriminator disc = null;

if (discriminatorPos != null) {
SegmentImpl segImpl = (SegmentImpl) sequence.get(0);
disc = buildDiscriminator(discriminatorPos, segImpl.getSequence());
}

return new LoopImpl(minOccurs, maxOccurs, id, typeId, disc, sequence, title, descr);
return readTypeImplementation(reader,
() -> readSequence(reader, e -> readLoopSequenceEntry(e, sequence)),
descr -> whenExpected(reader, complexType, () -> {
Discriminator disc = null;
if (discriminatorPos != null) {
SegmentImpl segImpl = (SegmentImpl) sequence.get(0);
disc = buildDiscriminator(discriminatorPos, segImpl.getSequence());
}
return new LoopImpl(minOccurs, maxOccurs, id, typeId, disc, sequence, title, descr);
}));
}

void readLoopSequenceEntry(QName entryName, List<EDITypeImplementation> sequence) {
Expand Down Expand Up @@ -270,20 +275,20 @@ SegmentImpl readSegmentImplementation() {
String title = parseAttribute(reader, ATTR_TITLE, String::valueOf, null);

return readTypeImplementation(reader,
() -> readSequence(reader, e -> readPositionedSequenceEntry(e, sequence, true)),
descr -> whenExpected(reader, qnSegment, () -> {
Discriminator disc = buildDiscriminator(discriminatorPos, sequence);
SegmentImpl segment = new SegmentImpl(minOccurs,
maxOccurs,
typeId,
code,
disc,
sequence,
title,
descr);
implementedTypes.add(segment);
return segment;
}));
() -> readSequence(reader, e -> readPositionedSequenceEntry(e, sequence, true)),
descr -> whenExpected(reader, qnSegment, () -> {
Discriminator disc = buildDiscriminator(discriminatorPos, sequence);
SegmentImpl segment = new SegmentImpl(minOccurs,
maxOccurs,
typeId,
code,
disc,
sequence,
title,
descr);
implementedTypes.add(segment);
return segment;
}));
}

void readPositionedSequenceEntry(QName entryName, List<EDITypeImplementation> sequence, boolean composites) {
Expand Down Expand Up @@ -345,7 +350,10 @@ Discriminator buildDiscriminator(EDIElementPosition discriminatorPos,
return disc;
}

EDITypeImplementation getDiscriminatorElement(EDIElementPosition discriminatorPos, int position, List<EDITypeImplementation> sequence, String type) {
EDITypeImplementation getDiscriminatorElement(EDIElementPosition discriminatorPos,
int position,
List<EDITypeImplementation> sequence,
String type) {
if (position > 0 && position <= sequence.size()) {
return sequence.get(position - 1);
} else {
Expand All @@ -361,16 +369,16 @@ CompositeImpl readCompositeImplementation(XMLStreamReader reader) {
String title = parseAttribute(reader, ATTR_TITLE, String::valueOf, null);

return readTypeImplementation(reader,
() -> readSequence(reader, e -> readPositionedSequenceEntry(e, sequence, false)),
descr -> whenExpected(reader,
qnComposite,
() -> new CompositeImpl(minOccurs,
maxOccurs,
null,
position,
sequence,
title,
descr)));
() -> readSequence(reader, e -> readPositionedSequenceEntry(e, sequence, false)),
descr -> whenExpected(reader,
qnComposite,
() -> new CompositeImpl(minOccurs,
maxOccurs,
null,
position,
sequence,
title,
descr)));
}

void readSequence(XMLStreamReader reader, Consumer<QName> startHandler) {
Expand All @@ -391,16 +399,16 @@ ElementImpl readElementImplementation(XMLStreamReader reader) {
String title = parseAttribute(reader, ATTR_TITLE, String::valueOf, null);

return readTypeImplementation(reader,
() -> valueSet.set(super.readEnumerationValues(reader)),
descr -> whenExpected(reader,
qnElement,
() -> new ElementImpl(minOccurs,
maxOccurs,
(String) null,
position,
valueSet.get(),
title,
descr)));
() -> valueSet.set(super.readEnumerationValues(reader)),
descr -> whenExpected(reader,
qnElement,
() -> new ElementImpl(minOccurs,
maxOccurs,
(String) null,
position,
valueSet.get(),
title,
descr)));
}

<T> T whenExpected(XMLStreamReader reader, QName expected, Supplier<T> supplier) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public CompositeImpl(int minOccurs,
public CompositeImpl(EDIReference standardReference, int position, List<EDITypeImplementation> sequence) {
super(sequence, null, null);
this.setStandardReference(standardReference);
super.typeId = standard.getId();
this.position = position;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.List;
import java.util.Objects;

import io.xlate.edi.schema.EDIReference;
import io.xlate.edi.schema.implementation.Discriminator;
import io.xlate.edi.schema.implementation.EDITypeImplementation;
import io.xlate.edi.schema.implementation.LoopImplementation;
Expand Down Expand Up @@ -30,6 +31,13 @@ public LoopImpl(int minOccurs,
this.discriminator = discriminator;
}

public LoopImpl(EDIReference standardReference, List<EDITypeImplementation> sequence) {
super(sequence, null, null);
this.setStandardReference(standardReference);
this.code = standard.getCode();
this.discriminator = null;
}

@Override
public boolean equals(Object o) {
return super.equals(o) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.List;
import java.util.Objects;

import io.xlate.edi.schema.EDIReference;
import io.xlate.edi.schema.implementation.Discriminator;
import io.xlate.edi.schema.implementation.EDITypeImplementation;
import io.xlate.edi.schema.implementation.SegmentImplementation;
Expand Down Expand Up @@ -30,6 +31,13 @@ public SegmentImpl(int minOccurs,
this.discriminator = discriminator;
}

public SegmentImpl(EDIReference standardReference, List<EDITypeImplementation> sequence) {
super(sequence, null, null);
this.setStandardReference(standardReference);
this.code = standard.getCode();
this.discriminator = null;
}

@Override
public boolean equals(Object o) {
return super.equals(o) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ public boolean segmentEnd() {
EDIReference typeReference = null;

if (validator != null) {
validator.clearImplementationCandidates(this);
validator.validateSyntax(dialect, this, this, location, false);
validator.validateVersionConstraints(dialect, this, null);
typeReference = validator.getSegmentReference();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,21 @@ static class RevalidationNode {
final StaEDIStreamLocation location;

public RevalidationNode(UsageNode standard, UsageNode impl, CharSequence data, StaEDIStreamLocation location) {
super();
this.standard = standard;
this.impl = impl;
this.data = CharBuffer.allocate(data.length());
this.data.append(data);
this.data.flip();
this.location = location.copy();
}

public RevalidationNode(UsageNode standard, UsageNode impl, StaEDIStreamLocation location) {
this.standard = standard;
this.impl = impl;
this.data = null;
this.location = location.copy();
}

}

public static Validator forSchema(Schema schema, Schema containerSchema, boolean validateCodeValues, boolean formatElements) {
Expand Down Expand Up @@ -525,7 +532,7 @@ boolean handleSegment(CharSequence tag, UsageNode current, UsageNode currentImpl
handler.segmentError(current.getId(), current.getLink(), IMPLEMENTATION_UNUSED_SEGMENT_PRESENT);
// Save the currentImpl so that the search is resumed from the correct location
implNode = currentImpl;
} else if (implSegmentCandidates.size() == 1) {
} else if (isSingleSegmentWithoutDescriminator(implSegmentCandidates)) {
currentImpl.incrementUsage();
currentImpl.resetChildren();

Expand All @@ -542,6 +549,15 @@ boolean handleSegment(CharSequence tag, UsageNode current, UsageNode currentImpl
return true;
}

boolean isSingleSegmentWithoutDescriminator(List<UsageNode> candidates) {
if (candidates.size() != 1) {
return false;
}

PolymorphicImplementation candidate = (PolymorphicImplementation) candidates.get(0).getLink();
return candidate.getDiscriminator() == null;
}

static UsageNode toSegment(UsageNode node) {
UsageNode segmentNode;

Expand Down Expand Up @@ -747,6 +763,13 @@ private void handleMissingMandatory(ValidationEventHandler handler, int depth) {
}
}

public void clearImplementationCandidates(ValidationEventHandler handler) {
if (!implSegmentCandidates.isEmpty()) {
handler.segmentError(segment.getId(), segment.getLink(), IMPLEMENTATION_UNUSED_SEGMENT_PRESENT);
implSegmentCandidates.clear();
}
}

public boolean selectImplementation(Deque<StreamEvent> eventQueue, ValidationEventHandler handler) {
StreamEvent currentEvent = eventQueue.getLast();

Expand Down Expand Up @@ -839,7 +862,7 @@ void checkPreviousSiblings(UsageNode implSeg, ValidationEventHandler handler) {
if (std.isUsed()) {
validateImplUnusedElementBlank(std, impl, true);
} else {
validateDataElementRequirement(null, std, impl, entry.data, entry.location);
validateDataElementRequirement(null, std, impl, entry.location);
}

handleRevalidatedElementErrors(entry, elementErrors, handler);
Expand Down Expand Up @@ -1060,7 +1083,7 @@ public boolean validateElement(Dialect dialect, StaEDIStreamLocation position, C
if (valueReceived) {
validateElementValue(dialect, position, value, formattedValue);
} else {
validateDataElementRequirement(version, this.element, this.implElement, value, position);
validateDataElementRequirement(version, this.element, this.implElement, position);
}

return elementErrors.isEmpty();
Expand Down Expand Up @@ -1121,7 +1144,11 @@ void validateElementValue(Dialect dialect, StaEDIStreamLocation position, CharSe

public void validateVersionConstraints(Dialect dialect, ValidationEventHandler validationHandler, StringBuilder formattedValue) {
for (RevalidationNode entry : revalidationQueue) {
validateElementValue(dialect, entry.standard, entry.impl, entry.data, formattedValue);
if (entry.data != null) {
validateElementValue(dialect, entry.standard, entry.impl, entry.data, formattedValue);
} else {
validateDataElementRequirement(dialect.getTransactionVersionString(), entry.standard, entry.impl, entry.location);
}
handleRevalidatedElementErrors(entry, elementErrors, validationHandler);
}

Expand Down Expand Up @@ -1257,12 +1284,12 @@ boolean validateImplUnusedElementBlank(UsageNode node, UsageNode implNode, boole
return true;
}

void validateDataElementRequirement(String version, UsageNode element, UsageNode implElement, CharSequence value, StaEDIStreamLocation position) {
void validateDataElementRequirement(String version, UsageNode element, UsageNode implElement, StaEDIStreamLocation position) {
if (!UsageNode.hasMinimumUsage(version, element) || !UsageNode.hasMinimumUsage(version, implElement)) {
elementErrors.add(new UsageError(element, REQUIRED_DATA_ELEMENT_MISSING));
} else if (isPendingDiscrimination()) {
// This element requirement can not be validated until the correct implementation is determined
revalidationQueue.add(new RevalidationNode(this.element, this.implElement, value, position));
revalidationQueue.add(new RevalidationNode(this.element, this.implElement, position));
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/schema/EDISchema-v3.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@
<complexContent>
<extension base="tns:loopBase">
<sequence>
<element name="sequence">
<element name="sequence" minOccurs="0">
<annotation>
<documentation>
The ordered list of segments and sub-loops contained in this
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/schema/EDISchema-v4.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@
<complexContent>
<extension base="tns:loopBase">
<sequence>
<element name="sequence">
<element name="sequence" minOccurs="0">
<annotation>
<documentation>
The ordered list of segments and sub-loops contained in this
Expand Down
Loading

0 comments on commit 248026d

Please sign in to comment.