Skip to content

Commit

Permalink
Add option TRUNCATE_EMPTY_ELEMENTS to suppress trailing empty elements
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeEdgar committed Jun 27, 2020
1 parent d9dcba3 commit 1e5dd2e
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 11 deletions.
Expand Up @@ -36,6 +36,7 @@ public StaEDIOutputFactory() {
supportedProperties.add(EDIStreamConstants.Delimiters.DECIMAL);
supportedProperties.add(EDIStreamConstants.Delimiters.RELEASE);
supportedProperties.add(PRETTY_PRINT);
supportedProperties.add(TRUNCATE_EMPTY_ELEMENTS);

properties.put(PRETTY_PRINT, Boolean.FALSE);
}
Expand Down
111 changes: 101 additions & 10 deletions src/main/java/io/xlate/edi/internal/stream/StaEDIStreamWriter.java
Expand Up @@ -30,8 +30,8 @@
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.logging.Logger;

import io.xlate.edi.internal.stream.tokenization.CharacterClass;
import io.xlate.edi.internal.stream.tokenization.CharacterSet;
Expand All @@ -57,6 +57,8 @@

public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler, ValidationEventHandler {

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

private static final int LEVEL_INITIAL = 0;
private static final int LEVEL_INTERCHANGE = 1;
private static final int LEVEL_SEGMENT = 2;
Expand Down Expand Up @@ -93,14 +95,24 @@ public class StaEDIStreamWriter implements EDIStreamWriter, ElementDataHandler,
private char decimalMark;
private char releaseIndicator;

private final boolean emptyElementTruncation;
private final boolean prettyPrint;
private final String lineSeparator;

private long elementLength = 0;

private int emptyElements = 0;
private boolean unterminatedElement = false;

private int emptyComponents = 0;
private boolean unterminatedComponent = false;

public StaEDIStreamWriter(OutputStream stream, Charset charset, Map<String, Object> properties) {
this.stream = stream;
this.writer = new OutputStreamWriter(stream, charset);
this.properties = new HashMap<>(properties);
this.prettyPrint = property(EDIOutputFactory.PRETTY_PRINT, Boolean::valueOf);
this.emptyElementTruncation = booleanValue(properties.get(EDIOutputFactory.TRUNCATE_EMPTY_ELEMENTS));
this.prettyPrint = booleanValue(properties.get(EDIOutputFactory.PRETTY_PRINT));

if (prettyPrint) {
lineSeparator = System.getProperty("line.separator");
Expand All @@ -110,13 +122,18 @@ public StaEDIStreamWriter(OutputStream stream, Charset charset, Map<String, Obje
this.location = new StaEDIStreamLocation();
}

@SuppressWarnings("unchecked")
private <T> T property(String key, Function<String, T> converter) {
Object prop = properties.get(key);
if (prop instanceof String) {
return converter.apply((String) prop);
boolean booleanValue(Object value) {
if (value instanceof Boolean) {
return (Boolean) value;
}
if (value instanceof String) {
return Boolean.valueOf(value.toString());
}
return (T) prop;
if (value == null) {
return false;
}
LOGGER.warning(() -> "Value [" + value + "] could not be converted to boolean");
return false;
}

private void setupDelimiters() {
Expand Down Expand Up @@ -375,6 +392,9 @@ public EDIStreamWriter writeStartSegment(String name) throws EDIStreamException
}

level = LEVEL_SEGMENT;
emptyElements = 0;
// Treat the segment tag as an unterminated element that must be closed when element data is encountered
unterminatedElement = true;

return this;
}
Expand Down Expand Up @@ -436,10 +456,17 @@ public EDIStreamWriter writeEndSegment() throws EDIStreamException {
@Override
public EDIStreamWriter writeStartElement() throws EDIStreamException {
ensureLevel(LEVEL_SEGMENT);
write(this.dataElementSeparator);
level = LEVEL_ELEMENT;
location.incrementElementPosition();
elementBuffer.clear();
elementLength = 0;
emptyComponents = 0;
unterminatedComponent = false;

if (!emptyElementTruncation) {
write(this.dataElementSeparator);
}

return this;
}

Expand All @@ -454,8 +481,13 @@ public EDIStreamWriter writeStartElementBinary() throws EDIStreamException {
public EDIStreamWriter writeRepeatElement() throws EDIStreamException {
ensureLevelAtLeast(LEVEL_SEGMENT);
write(this.repetitionSeparator);
// The repetition separator was used instead of the data element separator
unterminatedElement = false;
level = LEVEL_ELEMENT;
location.incrementElementOccurrence();
elementLength = 0;
emptyComponents = 0;
unterminatedComponent = false;
return this;
}

Expand All @@ -474,6 +506,12 @@ public EDIStreamWriter endElement() throws EDIStreamException {
location.clearComponentPosition();
level = LEVEL_SEGMENT;

if (elementLength > 0) {
unterminatedElement = true;
} else {
emptyElements++;
}

if (state == State.ELEMENT_DATA_BINARY) {
state = State.ELEMENT_END_BINARY;
}
Expand All @@ -489,22 +527,31 @@ public EDIStreamWriter startComponent() throws EDIStreamException {
throw new IllegalStateException();
}

if (LEVEL_COMPOSITE == level) {
if (LEVEL_COMPOSITE == level && !emptyElementTruncation) {
write(this.componentElementSeparator);
}

level = LEVEL_COMPONENT;
location.incrementComponentPosition();
elementBuffer.clear();
elementLength = 0;
return this;
}

@Override
public EDIStreamWriter endComponent() throws EDIStreamException {
ensureLevel(LEVEL_COMPONENT);

if (!atomicElementWrite) {
validateElement(this.elementBuffer::flip, this.elementBuffer);
}

if (elementLength > 0) {
unterminatedComponent = true;
} else {
emptyComponents++;
}

level = LEVEL_COMPOSITE;
return this;
}
Expand Down Expand Up @@ -573,9 +620,43 @@ public EDIStreamWriter writeEmptyComponent() throws EDIStreamException {
return this;
}

void writeRequiredSeparators(int dataLength) throws EDIStreamException {
if (dataLength < 1 || !emptyElementTruncation) {
return;
}

if (level >= LEVEL_ELEMENT) {
for (int i = 0; i < emptyElements; i++) {
write(this.dataElementSeparator);
}

if (unterminatedElement) {
write(this.dataElementSeparator);
}

emptyElements = 0;
unterminatedElement = false;
}

if (level == LEVEL_COMPONENT) {
for (int i = 0; i < emptyComponents; i++) {
write(this.componentElementSeparator);
}

if (unterminatedComponent) {
write(this.componentElementSeparator);
}

emptyComponents = 0;
unterminatedComponent = false;
}
}

@Override
public EDIStreamWriter writeElementData(CharSequence text) throws EDIStreamException {
ensureLevelAtLeast(LEVEL_ELEMENT);
writeRequiredSeparators(text.length());

for (int i = 0, m = text.length(); i < m; i++) {
char curr = text.charAt(i);

Expand All @@ -589,6 +670,7 @@ public EDIStreamWriter writeElementData(CharSequence text) throws EDIStreamExcep

write(curr);
elementBuffer.put(curr);
elementLength++;
}
return this;
}
Expand All @@ -597,6 +679,7 @@ public EDIStreamWriter writeElementData(CharSequence text) throws EDIStreamExcep
public EDIStreamWriter writeElementData(char[] text, int start, int end) throws EDIStreamException {
ensureLevelAtLeast(LEVEL_ELEMENT);
ensureArgs(text.length, start, end);
writeRequiredSeparators(end - start);

for (int i = start, m = end; i < m; i++) {
char curr = text[i];
Expand All @@ -605,6 +688,7 @@ public EDIStreamWriter writeElementData(char[] text, int start, int end) throws
}
write(curr);
elementBuffer.put(curr);
elementLength++;
}

return this;
Expand All @@ -617,11 +701,14 @@ public EDIStreamWriter writeBinaryData(InputStream binaryStream) throws EDIStrea
int output;

try {
writeRequiredSeparators(binaryStream.available());

flush(); // Write `Writer` buffers to stream before writing binary

while ((output = binaryStream.read()) != -1) {
location.incrementOffset(output);
stream.write(output);
elementLength++;
}
} catch (IOException e) {
throw new EDIStreamException("Exception writing binary element data", location, e);
Expand All @@ -635,13 +722,15 @@ public EDIStreamWriter writeBinaryData(byte[] binary, int start, int end) throws
ensureLevel(LEVEL_ELEMENT);
ensureState(State.ELEMENT_DATA_BINARY);
ensureArgs(binary.length, start, end);
writeRequiredSeparators(end - start);

try {
flush(); // Write `Writer` buffers to stream before writing binary

for (int i = start; i < end; i++) {
location.incrementOffset(binary[i]);
stream.write(binary[i]);
elementLength++;
}
} catch (IOException e) {
throw new EDIStreamException("Exception writing binary element data", location, e);
Expand All @@ -654,9 +743,11 @@ public EDIStreamWriter writeBinaryData(byte[] binary, int start, int end) throws
public EDIStreamWriter writeBinaryData(ByteBuffer binary) throws EDIStreamException {
ensureLevel(LEVEL_ELEMENT);
ensureState(State.ELEMENT_DATA_BINARY);
writeRequiredSeparators(binary.remaining());

while (binary.hasRemaining()) {
write(binary.get());
elementLength++;
}

return this;
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/io/xlate/edi/stream/EDIOutputFactory.java
Expand Up @@ -21,8 +21,23 @@

public abstract class EDIOutputFactory extends PropertySupport {

/**
* <p>When set to true, the EDI output will have a platform specific line
* separator written after each segment terminator.
*
* <p>Default value is false.
*/
public static final String PRETTY_PRINT = "io.xlate.edi.stream.PRETTY_PRINT";

/**
* <p>When set to true, empty trailing elements in a segment and empty trailing
* components in a composite element will be truncated. I.e, they will not
* be written to the output.
*
* <p>Default value is false.
*/
public static final String TRUNCATE_EMPTY_ELEMENTS = "io.xlate.edi.stream.TRUNCATE_EMPTY_ELEMENTS";

/**
* Create a new instance of the factory. This static method creates a new
* factory instance.
Expand Down

0 comments on commit 1e5dd2e

Please sign in to comment.