Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add getStandard method to EDIStreamWriter #26

Merged
merged 4 commits into from
May 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,16 @@ jobs:
run: mvn -B verify javadoc:javadoc --file pom.xml

quality:
needs: [build, javadoc]
if: github.event_name == 'push' && github.repository == 'xlate/staedi'
runs-on: ubuntu-latest
name: Verify Quality

steps:
- uses: actions/checkout@v2
with:
# Disabling shallow clone is recommended for improving relevancy of reporting
fetch-depth: 0

- uses: actions/setup-java@v1.3.0
with:
java-version: 11
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<groupId>io.xlate</groupId>
<artifactId>staedi</artifactId>
<version>1.6.1-SNAPSHOT</version>
<version>1.7.0-SNAPSHOT</version>

<name>StAEDI : Streaming API for EDI for Java</name>
<description>Streaming API for EDI for Java</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public EDIStreamEvent getEventType() {

@Override
public String getStandard() {
if (lexer.getDialect() == null || lexer.getDialect().getStandard() == null) {
if (lexer.getDialect() == null) {
throw new IllegalStateException("standard not accessible");
}

Expand Down
18 changes: 12 additions & 6 deletions src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ static char getDelimiter(Map<String, Object> properties, String key, Supplier<Ch
}

private static void ensureArgs(int arrayLength, int start, int end) {
if (start < 0 || start > arrayLength || end > arrayLength) {
if (start < 0 || start >= arrayLength || end > arrayLength) {
throw new IndexOutOfBoundsException();
}

Expand Down Expand Up @@ -205,6 +205,15 @@ public Location getLocation() {
return location;
}

@Override
public String getStandard() {
if (dialect == null) {
throw new IllegalStateException("standard not accessible");
}

return dialect.getStandard();
}

private Validator validator() {
// Do not use the transactionValidator in the period where it may be set/mutated by the user
return transaction && !transactionSchemaAllowed ? transactionValidator : controlValidator;
Expand All @@ -216,10 +225,7 @@ private void write(int output) throws EDIStreamException {
clazz = characters.getClass(output);

if (clazz == CharacterClass.INVALID) {
StringBuilder message = new StringBuilder();
message.append("Invalid character: 0x");
message.append(Integer.toHexString(output));
throw new EDIException(message.toString());
throw new EDIStreamException(String.format("Invalid character: 0x%04X", output), location);
}

state = state.transition(clazz);
Expand Down Expand Up @@ -251,7 +257,7 @@ private void write(int output) throws EDIStreamException {
}
}
} else {
throw new EDIException("Unexpected header character: '" + (char) output + "'");
throw new EDIStreamException(String.format("Unexpected header character: 0x%04X [%s]", output, (char) output), location);
}
break;
case INVALID:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,6 @@ private void enqueueEvent(EDIStreamEvent ediEvent) throws XMLStreamException {
this.location);

case ELEMENT_OCCURRENCE_ERROR:
throw new XMLStreamException(String.format("Element %s has error %s",
ediReader.getText(),
ediReader.getErrorType()),
this.location);

case ELEMENT_DATA_ERROR:
throw new XMLStreamException(String.format("Element %s has error %s",
ediReader.getText(),
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/io/xlate/edi/stream/EDIStreamException.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ private static String displayLocation(Location location) {
display.append(", element ");
display.append(String.valueOf(location.getElementPosition()));

if (location.getElementOccurrence() > -1) {
display.append("(occurrence ");
if (location.getElementOccurrence() > 1) {
display.append(" (occurrence ");
display.append(String.valueOf(location.getElementOccurrence()));
display.append(')');
}
Expand Down
39 changes: 28 additions & 11 deletions src/main/java/io/xlate/edi/stream/EDIStreamWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ public interface EDIStreamWriter {
* Calls to this method are only valid before the interchange is started.
*
* @param controlSchema
* the schema instance to use for validation of control structures
* the schema instance to use for validation of control
* structures
* @throws IllegalStateException
* when the writer is not in its initial state
*
Expand All @@ -71,32 +72,48 @@ public interface EDIStreamWriter {
* <p>
* Sets the schema to be used for validation of the business transaction for
* this stream writer. This schema will be used to validate only the
* contents of a transaction/message, <em>not including</em> the begin/end control
* structures.
* contents of a transaction/message, <em>not including</em> the begin/end
* control structures.
* <p>
* This method may be called at any time. However, when non-null, the writer will make
* use of the transaction schema for output validation. It is the responsibility of the
* caller to set the transaction schema to null at the end of the business transaction.
* This method may be called at any time. However, when non-null, the writer
* will make use of the transaction schema for output validation. It is the
* responsibility of the caller to set the transaction schema to null at the
* end of the business transaction.
*
* @param transactionSchema
* the schema instance to use for validation of business transaction structures
* the schema instance to use for validation of business
* transaction structures
*
* @since 1.1
*/
void setTransactionSchema(Schema transactionSchema);

/**
* Return the current location of the writer. If the Location is unknown
* the processor should return an implementation of Location that returns -1
* for the location values. The location information is only valid until
* the next item is written to the output.
* Return the current location of the writer. If the Location is unknown the
* processor should return an implementation of Location that returns -1 for
* the location values. The location information is only valid until the
* next item is written to the output.
*
* @return current location of the writer
*
* @since 1.1
*/
Location getLocation();

/**
* Get the EDI standard name. Calls to this method are only valid when the
* interchange type has been determined, after the full interchange header
* segment has been written.
*
* @return the name of the EDI standard
* @throws IllegalStateException
* when the standard has not yet been determined, prior to the
* start of an interchange header segment being fully written
*
* @since 1.7
*/
String getStandard();

EDIStreamWriter startInterchange() throws EDIStreamException;

EDIStreamWriter endInterchange() throws EDIStreamException;
Expand Down
151 changes: 143 additions & 8 deletions src/test/java/io/xlate/edi/internal/stream/StaEDIStreamWriterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;

Expand All @@ -31,6 +32,7 @@
import java.util.Map;

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import io.xlate.edi.internal.schema.SchemaUtils;
import io.xlate.edi.schema.EDISchemaException;
Expand All @@ -45,6 +47,7 @@
import io.xlate.edi.stream.EDIStreamValidationError;
import io.xlate.edi.stream.EDIStreamWriter;
import io.xlate.edi.stream.EDIValidationException;
import io.xlate.edi.stream.Location;

@SuppressWarnings("resource")
class StaEDIStreamWriterTest {
Expand Down Expand Up @@ -158,6 +161,28 @@ void testWriteStartSegment() throws EDIStreamException {
assertEquals("ISA", stream.toString());
}

@Test
void testWriteInvalidHeaderElement() throws EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
OutputStream stream = new ByteArrayOutputStream(4096);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
writer.startInterchange();
writer.writeStartSegment("ISA");
writer.writeElement("00").writeElement(" "); // Too long
writer.writeElement("00").writeElement(" "); // Too long
writer.writeElement("ZZ").writeElement("ReceiverID ");
writer.writeElement("ZZ").writeElement("Sender ");
writer.writeElement("050812");
writer.writeElement("1953");
writer.writeElement("^");
writer.writeElement("00501");
writer.writeElement("508121953");
writer.writeElement("0");
writer.writeElement("P");
EDIStreamException thrown = assertThrows(EDIStreamException.class, () -> writer.writeElement(":"));
assertEquals("Unexpected header character: 0x002A [*] in segment ISA at position 1, element 15", thrown.getMessage());
}

@Test
void testWriteStartSegmentIllegal() throws EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
Expand Down Expand Up @@ -220,6 +245,55 @@ void testWriteStartElementIllegal() throws EDIStreamException {
assertThrows(IllegalStateException.class, () -> writer.writeStartElement());
}

@Test
void testWriteInvalidCharacter() throws EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
OutputStream stream = new ByteArrayOutputStream(4096);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
writer.startInterchange();
writer.writeStartSegment("ISA");
writer.writeStartElement();
EDIStreamException thrown = assertThrows(EDIStreamException.class,
() -> writer.writeElementData("\u0008\u0010"));
assertEquals("Invalid character: 0x0008 in segment ISA at position 1, element 1", thrown.getMessage());
}

@Test
void testWriteInvalidCharacterRepeatedComposite() throws EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
OutputStream stream = new ByteArrayOutputStream(4096);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
writer.startInterchange();
writeHeader(writer);
writer.writeStartSegment("FOO");
writer.writeElement("BAR1");
writer.writeRepeatElement(); // starts new element
writer.writeComponent("BAR2");
writer.writeComponent("BAR3");
EDIStreamException thrown = assertThrows(EDIStreamException.class,
() -> writer.writeComponent("\u0008\u0010"));
assertEquals("Invalid character: 0x0008 in segment FOO at position 2, element 1 (occurrence 2), component 3", thrown.getMessage());
Location l = thrown.getLocation();
assertEquals("FOO", l.getSegmentTag());
assertEquals(2, l.getSegmentPosition());
assertEquals(1, l.getElementPosition());
assertEquals(2, l.getElementOccurrence());
assertEquals(3, l.getComponentPosition());
}

@Test
void testWriteInvalidSegmentTag() throws EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
OutputStream stream = new ByteArrayOutputStream(4096);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
writer.startInterchange();
writeHeader(writer);
writer.writeStartSegment("G");
EDIStreamException thrown = assertThrows(EDIStreamException.class,
() -> writer.writeElement("FOO"));
assertEquals("Invalid state: INVALID; output 0x002A", thrown.getMessage());
}

@Test
void testWriteStartElementBinary() throws IllegalStateException, EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
Expand Down Expand Up @@ -357,6 +431,21 @@ void testWriteElementDataCharArray() throws EDIStreamException {
assertEquals("ISA*CHARS~", stream.toString());
}

@Test
void testWriteElementDataCharInvalidBoundaries() throws EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
OutputStream stream = new ByteArrayOutputStream(4096);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
writer.startInterchange();
writeHeader(writer);
writer.writeStartSegment("GS");
writer.writeStartElement();
assertThrows(IndexOutOfBoundsException.class, () -> writer.writeElementData(new char[] { 'F', 'A' }, -1, 2));
assertThrows(IndexOutOfBoundsException.class, () -> writer.writeElementData(new char[] { 'F', 'A' }, 2, 1));
assertThrows(IndexOutOfBoundsException.class, () -> writer.writeElementData(new char[] { 'F', 'A' }, 0, 3));
assertThrows(IllegalArgumentException.class, () -> writer.writeElementData(new char[] { 'F', 'A' }, 0, -1));
}

@Test
void testWriteElementDataCharArrayIllegal() throws EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
Expand Down Expand Up @@ -390,6 +479,29 @@ void testWriteBinaryDataInputStream() throws EDIStreamException {
assertEquals("BIN*8*\n\u0000\u0001\u0002\u0003\u0004\u0005\t~", stream.toString());
}

@Test
void testWriteBinaryDataInputStreamIOException() throws Exception {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
ByteArrayOutputStream stream = new ByteArrayOutputStream(4096);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
InputStream binaryStream = Mockito.mock(InputStream.class);
IOException ioException = new IOException();
Mockito.when(binaryStream.read()).thenThrow(ioException);
writer.startInterchange();
writeHeader(writer);
stream.reset();
writer.writeStartSegment("BIN");
writer.writeStartElement();
writer.writeElementData("4");
writer.endElement();
writer.writeStartElementBinary();
EDIStreamException thrown = assertThrows(EDIStreamException.class,
() -> writer.writeBinaryData(binaryStream));
assertEquals("Exception writing binary element data in segment BIN at position 2, element 2",
thrown.getMessage());
assertSame(ioException, thrown.getCause());
}

@Test
void testWriteBinaryDataByteArray() throws EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
Expand Down Expand Up @@ -1055,19 +1167,42 @@ public int read() throws IOException {
break;
}

assertEquals(reader.getLocation().getSegmentPosition(), writer.getLocation().getSegmentPosition(), () ->
"Segment position mismatch");
assertEquals(reader.getLocation().getElementPosition(), writer.getLocation().getElementPosition(), () ->
"Element position mismatch");
assertEquals(reader.getLocation().getElementOccurrence(), writer.getLocation().getElementOccurrence(), () ->
"Element occurrence mismatch");
assertEquals(reader.getLocation().getComponentPosition(), writer.getLocation().getComponentPosition(), () ->
"Component position mismatch");
assertEquals(reader.getLocation().getSegmentPosition(),
writer.getLocation().getSegmentPosition(),
() -> "Segment position mismatch");
assertEquals(reader.getLocation().getElementPosition(),
writer.getLocation().getElementPosition(),
() -> "Element position mismatch");
assertEquals(reader.getLocation().getElementOccurrence(),
writer.getLocation().getElementOccurrence(),
() -> "Element occurrence mismatch");
assertEquals(reader.getLocation().getComponentPosition(),
writer.getLocation().getComponentPosition(),
() -> "Component position mismatch");
}
} finally {
reader.close();
}

assertEquals(expected.toString().trim(), result.toString().trim());
}

@Test
void testGetStandardNullDialect() {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
OutputStream stream = new ByteArrayOutputStream(1);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> writer.getStandard());
assertEquals("standard not accessible", thrown.getMessage());
}

@Test
void testGetStandardX12() throws EDIStreamException {
EDIOutputFactory factory = EDIOutputFactory.newFactory();
OutputStream stream = new ByteArrayOutputStream(1);
EDIStreamWriter writer = factory.createEDIStreamWriter(stream);
writer.startInterchange();
writer.writeStartSegment("ISA");
assertEquals(EDIStreamConstants.Standards.X12, writer.getStandard());
}
}