Skip to content

Commit

Permalink
Merge pull request #301 from MikeEdgar/issue256
Browse files Browse the repository at this point in the history
Validate control references and counters
  • Loading branch information
MikeEdgar authored Oct 7, 2022
2 parents a4b49ee + 7678850 commit e63492d
Show file tree
Hide file tree
Showing 36 changed files with 541 additions and 96 deletions.
58 changes: 58 additions & 0 deletions src/main/java/io/xlate/edi/internal/schema/ControlType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.xlate.edi.internal.schema;

import java.util.List;

import io.xlate.edi.schema.EDIControlType;
import io.xlate.edi.schema.EDIElementPosition;
import io.xlate.edi.schema.EDIReference;
import io.xlate.edi.schema.EDISyntaxRule;
import io.xlate.edi.schema.EDIType;

@SuppressWarnings("java:S2160") // Intentionally inherit 'equals' from superclass
class ControlType extends StructureType implements EDIControlType {

private final EDIElementPosition headerRefPosition;
private final EDIElementPosition trailerRefPosition;
private final EDIElementPosition trailerCountPosition;
private final EDIControlType.Type countType;

@SuppressWarnings("java:S107")
ControlType(String id,
EDIType.Type type,
String code,
List<EDIReference> references,
List<EDISyntaxRule> syntaxRules,
EDIElementPosition headerRefPosition,
EDIElementPosition trailerRefPosition,
EDIElementPosition trailerCountPosition,
EDIControlType.Type countType,
String title,
String description) {

super(id, type, code, references, syntaxRules, title, description);
this.headerRefPosition = headerRefPosition;
this.trailerRefPosition = trailerRefPosition;
this.trailerCountPosition = trailerCountPosition;
this.countType = countType;
}

@Override
public EDIElementPosition getHeaderRefPosition() {
return headerRefPosition;
}

@Override
public EDIElementPosition getTrailerRefPosition() {
return trailerRefPosition;
}

@Override
public EDIElementPosition getTrailerCountPosition() {
return trailerCountPosition;
}

@Override
public EDIControlType.Type getCountType() {
return countType;
}
}
105 changes: 76 additions & 29 deletions src/main/java/io/xlate/edi/internal/schema/SchemaReaderBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import javax.xml.stream.XMLStreamReader;

import io.xlate.edi.schema.EDIComplexType;
import io.xlate.edi.schema.EDIControlType;
import io.xlate.edi.schema.EDIElementPosition;
import io.xlate.edi.schema.EDIReference;
import io.xlate.edi.schema.EDISchemaException;
Expand All @@ -48,6 +49,10 @@ abstract class SchemaReaderBase implements SchemaReader {
static final String ATTR_MIN_OCCURS = "minOccurs";
static final String ATTR_MAX_OCCURS = "maxOccurs";
static final String ATTR_TITLE = "title";
static final String ATTR_HEADER_REF_POSITION = "headerRefPosition";
static final String ATTR_TRAILER_REF_POSITION = "trailerRefPosition";
static final String ATTR_TRAILER_COUNT_POSITION = "trailerCountPosition";
static final String ATTR_COUNT_TYPE = "countType";
static final String ATTR_LEVEL_ID_POSITION = "levelIdPosition";
static final String ATTR_PARENT_ID_POSITION = "parentIdPosition";

Expand Down Expand Up @@ -216,8 +221,13 @@ String readDescription(XMLStreamReader reader) {
void readInterchange(XMLStreamReader reader, Map<String, EDIType> types) {
QName element;

Reference headerRef = createControlReference(reader, "header");
Reference trailerRef = createControlReference(reader, "trailer");
Reference header = createControlReference(reader, "header");
Reference trailer = createControlReference(reader, "trailer");
EDIElementPosition headerRefPos = parseElementPosition(reader, ATTR_HEADER_REF_POSITION);
EDIElementPosition trailerRefPos = parseElementPosition(reader, ATTR_TRAILER_REF_POSITION);
EDIElementPosition trailerCountPos = parseElementPosition(reader, ATTR_TRAILER_COUNT_POSITION);
EDIControlType.Type countType = parseAttribute(reader, ATTR_COUNT_TYPE, EDIControlType.Type::fromString, EDIControlType.Type.NONE);

String title = parseAttribute(reader, ATTR_TITLE, String::valueOf, null);
String descr = readDescription(reader);

Expand All @@ -231,7 +241,7 @@ void readInterchange(XMLStreamReader reader, Map<String, EDIType> types) {
element = reader.getName();

List<EDIReference> refs = new ArrayList<>(3);
refs.add(headerRef);
refs.add(header);

while (qnSegment.equals(element)) {
addReferences(reader, EDIType.Type.SEGMENT, refs, readReference(reader, types));
Expand All @@ -254,7 +264,7 @@ void readInterchange(XMLStreamReader reader, Map<String, EDIType> types) {
element = reader.getName();
}

refs.add(trailerRef);
refs.add(trailer);

final List<EDISyntaxRule> rules;

Expand All @@ -265,13 +275,17 @@ void readInterchange(XMLStreamReader reader, Map<String, EDIType> types) {
rules = Collections.emptyList();
}

StructureType interchange = new StructureType(StaEDISchema.INTERCHANGE_ID,
EDIType.Type.INTERCHANGE,
"INTERCHANGE",
refs,
rules,
title,
descr);
StructureType interchange = new ControlType(StaEDISchema.INTERCHANGE_ID,
EDIType.Type.INTERCHANGE,
"INTERCHANGE",
refs,
rules,
headerRefPos,
trailerRefPos,
trailerCountPos,
countType,
title,
descr);

types.put(interchange.getId(), interchange);
nextTag(reader, "advancing after interchange");
Expand Down Expand Up @@ -299,8 +313,12 @@ Reference readControlStructure(XMLStreamReader reader,
throw schemaException("Invalid value for 'use': " + use, reader);
}

Reference headerRef = createControlReference(reader, "header");
Reference trailerRef = createControlReference(reader, "trailer");
Reference header = createControlReference(reader, "header");
Reference trailer = createControlReference(reader, "trailer");
EDIElementPosition headerRefPos = parseElementPosition(reader, ATTR_HEADER_REF_POSITION);
EDIElementPosition trailerRefPos = parseElementPosition(reader, ATTR_TRAILER_REF_POSITION);
EDIElementPosition trailerCountPos = parseElementPosition(reader, ATTR_TRAILER_COUNT_POSITION);
EDIControlType.Type countType = parseAttribute(reader, ATTR_COUNT_TYPE, EDIControlType.Type::fromString, EDIControlType.Type.NONE);
String title = parseAttribute(reader, ATTR_TITLE, String::valueOf, null);
String descr = readDescription(reader);

Expand All @@ -309,22 +327,26 @@ Reference readControlStructure(XMLStreamReader reader,
}

List<EDIReference> refs = new ArrayList<>(3);
refs.add(headerRef);
refs.add(header);
if (subelement != null) {
refs.add(readControlStructure(reader, subelement, null, types));
}
refs.add(trailerRef);
refs.add(trailer);

Type elementType = complex.get(element);
String elementId = StaEDISchema.ID_PREFIX + elementType.name();

StructureType struct = new StructureType(elementId,
elementType,
elementType.toString(),
refs,
Collections.emptyList(),
title,
descr);
StructureType struct = new ControlType(elementId,
elementType,
elementType.toString(),
refs,
Collections.emptyList(),
headerRefPos,
trailerRefPos,
trailerCountPos,
countType,
title,
descr);

types.put(struct.getId(), struct);

Expand Down Expand Up @@ -393,21 +415,38 @@ StructureType readComplexType(XMLStreamReader reader,
final EDIType.Type type = complex.get(complexType);
final String id;
String code = parseAttribute(reader, "code", String::valueOf, null);
// Transaction attributes
EDIElementPosition headerRef = null;
EDIElementPosition trailerRef = null;
EDIElementPosition trailerCount = null;
EDIControlType.Type countType = null;
// Loop attributes
EDIElementPosition levelIdPosition = null;
EDIElementPosition parentIdPosition = null;

if (qnTransaction.equals(complexType)) {
switch (type) {
case TRANSACTION:
id = StaEDISchema.TRANSACTION_ID;
} else if (qnLoop.equals(complexType)) {
headerRef = parseElementPosition(reader, ATTR_HEADER_REF_POSITION);
trailerRef = parseElementPosition(reader, ATTR_TRAILER_REF_POSITION);
trailerCount = parseElementPosition(reader, ATTR_TRAILER_COUNT_POSITION);
countType = parseAttribute(reader, ATTR_COUNT_TYPE, EDIControlType.Type::fromString, EDIControlType.Type.NONE);
break;
case LOOP:
id = code;
levelIdPosition = parseElementPosition(reader, ATTR_LEVEL_ID_POSITION);
parentIdPosition = parseElementPosition(reader, ATTR_PARENT_ID_POSITION);
} else {
break;
case SEGMENT:
id = parseAttribute(reader, "name", String::valueOf);

if (type == EDIType.Type.SEGMENT && !id.matches("^[A-Z][A-Z0-9]{1,2}$")) {
if (!id.matches("^[A-Z][A-Z0-9]{1,2}$")) {
throw schemaException("Invalid segment name [" + id + ']', reader);
}
break;
case COMPOSITE:
default: /* Only COMPOSITE remains */
id = parseAttribute(reader, "name", String::valueOf);
break;
}

if (code == null) {
Expand All @@ -434,10 +473,18 @@ StructureType readComplexType(XMLStreamReader reader,
if (event == XMLStreamConstants.END_ELEMENT) {
StructureType structure;

if (qnLoop.equals(complexType)) {
switch (type) {
case TRANSACTION:
structure = new ControlType(id, type, code, refs, rules, headerRef, trailerRef, trailerCount, countType, title, descr);
break;
case LOOP:
structure = new LoopType(code, refs, rules, levelIdPosition, parentIdPosition, title, descr);
} else {
break;
case SEGMENT:
case COMPOSITE:
default:
structure = new StructureType(id, type, code, refs, rules, title, descr);
break;
}

return structure;
Expand Down
5 changes: 0 additions & 5 deletions src/main/java/io/xlate/edi/internal/schema/StaEDISchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,6 @@ public synchronized int hashCode() {
return localHash.intValue();
}

@Override
public EDIComplexType getMainLoop() {
return getStandard();
}

@Override
public EDIComplexType getStandard() {
return standardLoop;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ public EDIStreamWriter endInterchange() throws EDIStreamException {
@Override
public EDIStreamWriter writeStartSegment(String name) throws EDIStreamException {
ensureLevel(LEVEL_INTERCHANGE);
location.incrementSegmentPosition(name);
countSegment(name);

if (state == State.INITIAL) {
dialect = DialectFactory.getDialect(name);
Expand Down Expand Up @@ -462,6 +462,14 @@ public EDIStreamWriter writeStartSegment(String name) throws EDIStreamException
return this;
}

void countSegment(String name) {
location.incrementSegmentPosition(name);

if (controlValidator != null) {
controlValidator.countSegment(name);
}
}

void segmentValidation(String name) {
validate(validator -> validator.validateSegment(this, name));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,10 @@ public boolean segmentBegin(String segmentTag) {
typeReference = validator().getSegmentReference();
}

if (controlValidator != null) {
controlValidator.countSegment(segmentTag);
}

enqueueEvent(EDIStreamEvent.START_SEGMENT, EDIStreamValidationError.NONE, segmentTag, typeReference, location);
currentSegmentBegin = eventQueue.getLast();
return !levelCheckPending && eventsReady;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package io.xlate.edi.internal.stream.validation;

import java.util.List;
import java.util.logging.Logger;

import io.xlate.edi.schema.EDIControlType;
import io.xlate.edi.schema.EDIElementPosition;
import io.xlate.edi.schema.EDIReference;
import io.xlate.edi.stream.EDIStreamValidationError;
import io.xlate.edi.stream.Location;

class ControlUsageNode extends UsageNode {

static final Logger LOGGER = Logger.getLogger(ControlUsageNode.class.getName());

String referenceValue;
EDIControlType type;
int count;

ControlUsageNode(UsageNode parent, int depth, EDIReference link, int siblingIndex) {
super(parent, depth, link, siblingIndex);
type = (EDIControlType) link.getReferencedType();
}

@Override
void reset() {
super.reset();
this.referenceValue = null;
this.count = 0;
}

@Override
void incrementUsage() {
super.incrementUsage();
this.referenceValue = null;
this.count = 0;
}

boolean matchesLocation(int segmentRef, EDIElementPosition position, Location location) {
return position != null
&& position.matchesLocation(location)
&& type.getReferences().get(segmentRef).getReferencedType().getId().equals(location.getSegmentTag());
}

void validateReference(Location location, CharSequence value, List<EDIStreamValidationError> errors) {
if (referenceValue == null) {
if (matchesLocation(0, type.getHeaderRefPosition(), location)) {
this.referenceValue = value.toString();
}
return;
}

if (matchesLocation(type.getReferences().size() - 1, type.getTrailerRefPosition(), location)
&& !referenceValue.contentEquals(value)) {
errors.add(EDIStreamValidationError.CONTROL_REFERENCE_MISMATCH);
}
}

void validateCount(Location location, CharSequence value, List<EDIStreamValidationError> errors) {
if (matchesLocation(type.getReferences().size() - 1, type.getTrailerCountPosition(), location)
// Don't bother comparing the actual value if it's not formatted correctly
&& !errors.contains(EDIStreamValidationError.INVALID_CHARACTER_DATA)
&& !String.valueOf(count).contentEquals(value)) {
errors.add(EDIStreamValidationError.CONTROL_COUNT_DOES_NOT_MATCH_ACTUAL_COUNT);
}
}

int incrementCount(EDIControlType.Type countType) {
if (this.type.getCountType() == countType) {
count++;
return count;
}
return 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -245,5 +245,4 @@ private UsageNode getChildById(CharSequence id) {
UsageNode getSiblingById(CharSequence id) {
return parent != null ? parent.getChildById(id) : null;
}

}
Loading

0 comments on commit e63492d

Please sign in to comment.