From b7c84d0f958716dbbf0d2e2ac09191b174dced43 Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Thu, 23 Jun 2016 11:11:03 -0400 Subject: [PATCH] Initial Commit --- .gitignore | 4 + pom.xml | 118 ++++++++++ .../com/moparisthebest/hl7/Component.java | 70 ++++++ .../java/com/moparisthebest/hl7/Encoding.java | 178 +++++++++++++++ .../java/com/moparisthebest/hl7/Field.java | 118 ++++++++++ .../java/com/moparisthebest/hl7/Line.java | 106 +++++++++ .../java/com/moparisthebest/hl7/Message.java | 213 ++++++++++++++++++ .../com/moparisthebest/hl7/Repetition.java | 78 +++++++ .../moparisthebest/hl7/RepetitionCounter.java | 50 ++++ .../com/moparisthebest/hl7/SubComponent.java | 115 ++++++++++ .../hl7/SubComponentContainer.java | 117 ++++++++++ .../moparisthebest/hl7/SubComponentImpl.java | 70 ++++++ 12 files changed, 1237 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/moparisthebest/hl7/Component.java create mode 100644 src/main/java/com/moparisthebest/hl7/Encoding.java create mode 100644 src/main/java/com/moparisthebest/hl7/Field.java create mode 100644 src/main/java/com/moparisthebest/hl7/Line.java create mode 100644 src/main/java/com/moparisthebest/hl7/Message.java create mode 100644 src/main/java/com/moparisthebest/hl7/Repetition.java create mode 100644 src/main/java/com/moparisthebest/hl7/RepetitionCounter.java create mode 100644 src/main/java/com/moparisthebest/hl7/SubComponent.java create mode 100644 src/main/java/com/moparisthebest/hl7/SubComponentContainer.java create mode 100644 src/main/java/com/moparisthebest/hl7/SubComponentImpl.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..765aad8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +*.iml +*/target/ +target/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0216922 --- /dev/null +++ b/pom.xml @@ -0,0 +1,118 @@ + + + + + org.sonatype.oss + oss-parent + 9 + + 4.0.0 + com.moparisthebest.hl7 + SimpleHL7 + jar + 1.0-SNAPSHOT + ${project.artifactId} + + This project makes creating, parsing, sending and recieving HL7 messages simple. + + https://github.com/moparisthebest/SimpleHL7 + + moparisthebest.com + https://www.moparisthebest.com + + + + moparisthebest + Travis Burtrum + admin@moparisthebest.com + https://www.moparisthebest.com/ + + + + scm:git:https://github.com/moparisthebest/SimpleHL7.git + scm:git:https://github.com/moparisthebest/SimpleHL7.git + https://github.com/moparisthebest/SimpleHL7 + + + + Mozilla Public License Version 1.1 + https://www.mozilla.org/en-US/MPL/1.1/ + + + GNU General Public License, Version 3 + https://www.gnu.org/licenses/gpl-3.0.html + + + + true + UTF-8 + false + true + + + ${project.artifactId} + + + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + true + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.2 + true + + ossrh + https://oss.sonatype.org/ + true + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.7 + + -Xdoclint:none + + + + + + + + run-tests + + + maven.test.skip + false + + + + + \ No newline at end of file diff --git a/src/main/java/com/moparisthebest/hl7/Component.java b/src/main/java/com/moparisthebest/hl7/Component.java new file mode 100644 index 0000000..abed8ec --- /dev/null +++ b/src/main/java/com/moparisthebest/hl7/Component.java @@ -0,0 +1,70 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.mozilla.org/MPL/ + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the + * specific language governing rights and limitations under the License. + * + * Copyright (c) 2016 Travis Burtrum. + * + * Alternatively, the contents of this file may be used under the terms of the + * GNU General Public License (the "GPL"), in which case the provisions of the GPL are + * applicable instead of those above. If you wish to allow use of your version of this + * file only under the terms of the GPL and not to allow others to use your version + * of this file under the MPL, indicate your decision by deleting the provisions above + * and replace them with the notice and other provisions required by the GPL License. + * If you do not delete the provisions above, a recipient may use your version of + * this file under either the MPL or the GPL. + */ + +package com.moparisthebest.hl7; + +public class Component extends SubComponentContainer { + + Component() { + super(); + } + + Component(final String s) { + super(s); + } + + Component(final String s, final Encoding enc) { + super(s, enc, enc.subComponentDelimiter); + } + + @Override + SubComponent newSubComponent() { + return new SubComponentImpl(); + } + + @Override + SubComponent newSubComponent(final String s) { + return new SubComponentImpl(s); + } + + @Override + SubComponent newSubComponent(final String s, final Encoding enc) { + return new SubComponentImpl(s, enc); + } + + public void encode(final StringBuilder sb, final Encoding enc) { + super.encode(sb, enc, enc.subComponentDelimiter, enc.componentDelimiter); + } + + public SubComponent subComponent(final int index) { + return super.subComponent1Based(index); + } + + // shorter aliases + + public SubComponent sc(final int index) { + return this.subComponent(index); + } + + @Override + public String toString() { + return super.toString("SubComponent"); + } +} diff --git a/src/main/java/com/moparisthebest/hl7/Encoding.java b/src/main/java/com/moparisthebest/hl7/Encoding.java new file mode 100644 index 0000000..5611da2 --- /dev/null +++ b/src/main/java/com/moparisthebest/hl7/Encoding.java @@ -0,0 +1,178 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.mozilla.org/MPL/ + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the + * specific language governing rights and limitations under the License. + * + * Copyright (c) 2016 Travis Burtrum. + * + * Alternatively, the contents of this file may be used under the terms of the + * GNU General Public License (the "GPL"), in which case the provisions of the GPL are + * applicable instead of those above. If you wish to allow use of your version of this + * file only under the terms of the GPL and not to allow others to use your version + * of this file under the MPL, indicate your decision by deleting the provisions above + * and replace them with the notice and other provisions required by the GPL License. + * If you do not delete the provisions above, a recipient may use your version of + * this file under either the MPL or the GPL. + */ + +package com.moparisthebest.hl7; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class Encoding extends Field { + public final char segmentDelimiter, repetitionDelimiter, fieldDelimiter, componentDelimiter, subComponentDelimiter, escapeCharacter, verticalTab, fileSeperator; + public final String repetitionEscape, fieldEscape, componentEscape, subComponentEscape, escapeEscape; + + private final String repetitionDelimiterString, fieldDelimiterString, componentDelimiterString, subComponentDelimiterString, escapeCharacterString, value; + + public final MSH1 msh1; + + // '\r', '~', '|', '^', '&', '\\' + public Encoding(final char segmentDelimiter, final char repetitionDelimiter, final char fieldDelimiter, final char componentDelimiter, final char subComponentDelimiter, final char escapeCharacter) { + this(segmentDelimiter, repetitionDelimiter, fieldDelimiter, componentDelimiter, subComponentDelimiter, escapeCharacter, (char) 11, (char) 28); + } + + public Encoding(final char segmentDelimiter, final char repetitionDelimiter, final char fieldDelimiter, final char componentDelimiter, final char subComponentDelimiter, final char escapeCharacter, final char verticalTab, final char fileSeperator) { + this.segmentDelimiter = segmentDelimiter; + this.repetitionDelimiter = repetitionDelimiter; + this.fieldDelimiter = fieldDelimiter; + this.componentDelimiter = componentDelimiter; + this.subComponentDelimiter = subComponentDelimiter; + this.escapeCharacter = escapeCharacter; + this.verticalTab = verticalTab; + this.fileSeperator = fileSeperator; + + this.repetitionEscape = escapeCharacter + "R" + escapeCharacter; + this.fieldEscape = escapeCharacter + "F" + escapeCharacter; + this.componentEscape = escapeCharacter + "S" + escapeCharacter; + this.subComponentEscape = escapeCharacter + "T" + escapeCharacter; + this.escapeEscape = escapeCharacter + "E" + escapeCharacter; + + this.repetitionDelimiterString = "" + repetitionDelimiter; + this.fieldDelimiterString = "" + fieldDelimiter; + this.componentDelimiterString = "" + componentDelimiter; + this.subComponentDelimiterString = "" + subComponentDelimiter; + this.escapeCharacterString = "" + escapeCharacter; + + this.value = componentDelimiterString + repetitionDelimiter + escapeCharacter + subComponentDelimiter; + this.msh1 = new MSH1(this.fieldDelimiterString); + } + + public String decode(final String s) { + return s.replace(repetitionEscape, repetitionDelimiterString) + .replace(fieldEscape, fieldDelimiterString) + .replace(componentEscape, componentDelimiterString) + .replace(subComponentEscape, subComponentDelimiterString) + .replace(escapeEscape, escapeCharacterString); + } + + public String encode(final String s) { + return s.replace(escapeCharacterString, escapeEscape) + .replace(repetitionDelimiterString, repetitionEscape) + .replace(fieldDelimiterString, fieldEscape) + .replace(componentDelimiterString, componentEscape) + .replace(subComponentDelimiterString, subComponentEscape); + } + + public Collection splitSegment(final String s) { + return split(s, segmentDelimiter); + } + + public Collection splitRepetition(final String s) { + return split(s, repetitionDelimiter); + } + + public Collection splitField(final String s) { + return split(s, fieldDelimiter); + } + + public Collection splitComponent(final String s) { + return split(s, componentDelimiter); + } + + public Collection splitSubComponent(final String s) { + return split(s, subComponentDelimiter); + } + + public Collection split(final String s, final char ch) { + int off = 0; + int next = 0; + final List list = new ArrayList<>(); + while ((next = s.indexOf(ch, off)) != -1) { + list.add(s.substring(off, next)); + off = next + 1; + } + + // Add remaining segment + list.add(s.substring(off, s.length())); + + return list; + } + + @Override + public void encode(final StringBuilder sb, final Encoding enc) { + sb.append(this.value); + sb.append(enc.fieldDelimiter); + } + + @Override + public String value() { + return value; + } + + @Override + public SubComponent value(final String value) { + // do nothing + return this; + } + + @Override + public boolean equals(final String s) { + return value.equals(s); + } + + @Override + public String toString() { + return value; + } + + static class MSH1 extends Field { + + private final String value; + + private MSH1(final String fieldDelimiter) { + this.value = fieldDelimiter; + } + + @Override + public void encode(final StringBuilder sb, final Encoding enc) { + // do nothing + } + + @Override + public String value() { + return value; + } + + @Override + public SubComponent value(final String value) { + // do nothing + return this; + } + + @Override + public boolean equals(final String s) { + return value.equals(s); + } + + @Override + public String toString() { + return value; + } + } +} diff --git a/src/main/java/com/moparisthebest/hl7/Field.java b/src/main/java/com/moparisthebest/hl7/Field.java new file mode 100644 index 0000000..0719e51 --- /dev/null +++ b/src/main/java/com/moparisthebest/hl7/Field.java @@ -0,0 +1,118 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.mozilla.org/MPL/ + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the + * specific language governing rights and limitations under the License. + * + * Copyright (c) 2016 Travis Burtrum. + * + * Alternatively, the contents of this file may be used under the terms of the + * GNU General Public License (the "GPL"), in which case the provisions of the GPL are + * applicable instead of those above. If you wish to allow use of your version of this + * file only under the terms of the GPL and not to allow others to use your version + * of this file under the MPL, indicate your decision by deleting the provisions above + * and replace them with the notice and other provisions required by the GPL License. + * If you do not delete the provisions above, a recipient may use your version of + * this file under either the MPL or the GPL. + */ + +package com.moparisthebest.hl7; + +public class Field extends SubComponentContainer { + + Field() { + super(); + } + + Field(final String s) { + super(s); + } + + Field(final String s, final Encoding enc) { + super(s, enc, enc.repetitionDelimiter); + } + + @Override + Repetition newSubComponent() { + return new Repetition(); + } + + @Override + Repetition newSubComponent(final String s) { + return new Repetition(s); + } + + @Override + Repetition newSubComponent(final String s, final Encoding enc) { + return new Repetition(s, enc); + } + + public void encode(final StringBuilder sb, final Encoding enc) { + super.encode(sb, enc, enc.repetitionDelimiter, enc.fieldDelimiter); + } + + public RepetitionCounter repetitionCounter() { + return new RepetitionCounter(this, 1); + } + + public RepetitionCounter repetitionCounter(final int startIndex) { + return new RepetitionCounter(this, startIndex); + } + + public Repetition repetition(final int index) { + return super.subComponent1Based(index); + } + + public Component component(final int repetitionIndex, final int componentIndex) { + return this.repetition(repetitionIndex).component(componentIndex); + } + + public Component component(final int index) { + return this.repetition(1).component(index); + } + + public SubComponent subComponent(final int repetitionIndex, final int componentIndex, final int subComponentIndex) { + return this.repetition(repetitionIndex).component(componentIndex).subComponent(subComponentIndex); + } + + public SubComponent subComponent(final int componentIndex, final int subComponentIndex) { + return this.component(componentIndex).subComponent(subComponentIndex); + } + + // shorter aliases + + public RepetitionCounter rc() { + return this.repetitionCounter(); + } + + public RepetitionCounter rc(final int startIndex) { + return this.repetitionCounter(startIndex); + } + + public Repetition r(final int repetitionIndex) { + return this.repetition(repetitionIndex); + } + + public Component c(final int repetitionIndex, final int componentIndex) { + return this.component(repetitionIndex, componentIndex); + } + + public Component c(final int componentIndex) { + return this.component(componentIndex); + } + + public SubComponent sc(final int repetitionIndex, final int componentIndex, final int subComponentIndex) { + return this.subComponent(repetitionIndex, componentIndex, subComponentIndex); + } + + public SubComponent sc(final int componentIndex, final int subComponentIndex) { + return this.subComponent(componentIndex, subComponentIndex); + } + + @Override + public String toString() { + return super.toString("Repetition"); + } +} diff --git a/src/main/java/com/moparisthebest/hl7/Line.java b/src/main/java/com/moparisthebest/hl7/Line.java new file mode 100644 index 0000000..670d2bd --- /dev/null +++ b/src/main/java/com/moparisthebest/hl7/Line.java @@ -0,0 +1,106 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.mozilla.org/MPL/ + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the + * specific language governing rights and limitations under the License. + * + * Copyright (c) 2016 Travis Burtrum. + * + * Alternatively, the contents of this file may be used under the terms of the + * GNU General Public License (the "GPL"), in which case the provisions of the GPL are + * applicable instead of those above. If you wish to allow use of your version of this + * file only under the terms of the GPL and not to allow others to use your version + * of this file under the MPL, indicate your decision by deleting the provisions above + * and replace them with the notice and other provisions required by the GPL License. + * If you do not delete the provisions above, a recipient may use your version of + * this file under either the MPL or the GPL. + */ + +package com.moparisthebest.hl7; + +public class Line extends SubComponentContainer { + + public Line(final String s) { + super(s); + } + + Line(final String s, final Encoding enc) { + super(s, enc, enc.fieldDelimiter); + } + + @Override + Field newSubComponent() { + return new Field(); + } + + @Override + Field newSubComponent(final String s) { + return new Field(s); + } + + @Override + Field newSubComponent(final String s, final Encoding enc) { + return new Field(s, enc); + } + + public void encode(final StringBuilder sb, final Encoding enc) { + super.encode(sb, enc, enc.fieldDelimiter, enc.segmentDelimiter); + } + + public Field field(final int index) { + return super.subComponent0Based(index); + } + + public Repetition repetition(final int fieldIndex, final int repetitionIndex) { + return this.field(fieldIndex).repetition(repetitionIndex); + } + + public Component component(final int fieldIndex, final int repetitionIndex, final int componentIndex) { + return this.field(fieldIndex).repetition(repetitionIndex).component(componentIndex); + } + + public Component component(final int fieldIndex, final int componentIndex) { + return this.field(fieldIndex).component(componentIndex); + } + + public SubComponent subComponent(final int fieldIndex, final int repetitionIndex, final int componentIndex, final int subComponentIndex) { + return this.field(fieldIndex).repetition(repetitionIndex).component(componentIndex).subComponent(subComponentIndex); + } + + public SubComponent subComponent(final int fieldIndex, final int componentIndex, final int subComponentIndex) { + return this.field(fieldIndex).component(componentIndex).subComponent(subComponentIndex); + } + + // shorter aliases + + public Field f(final int index) { + return this.field(index); + } + + public Repetition r(final int fieldIndex, final int repetitionIndex) { + return this.repetition(fieldIndex, repetitionIndex); + } + + public Component c(final int fieldIndex, final int repetitionIndex, final int componentIndex) { + return this.component(fieldIndex, repetitionIndex, componentIndex); + } + + public Component c(final int fieldIndex, final int componentIndex) { + return this.component(fieldIndex, componentIndex); + } + + public SubComponent sc(final int fieldIndex, final int repetitionIndex, final int componentIndex, final int subComponentIndex) { + return this.subComponent(fieldIndex, repetitionIndex, componentIndex, subComponentIndex); + } + + public SubComponent sc(final int fieldIndex, final int componentIndex, final int subComponentIndex) { + return this.subComponent(fieldIndex, componentIndex, subComponentIndex); + } + + @Override + public String toString() { + return super.toString("Line"); + } +} diff --git a/src/main/java/com/moparisthebest/hl7/Message.java b/src/main/java/com/moparisthebest/hl7/Message.java new file mode 100644 index 0000000..2e28a9f --- /dev/null +++ b/src/main/java/com/moparisthebest/hl7/Message.java @@ -0,0 +1,213 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.mozilla.org/MPL/ + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the + * specific language governing rights and limitations under the License. + * + * Copyright (c) 2016 Travis Burtrum. + * + * Alternatively, the contents of this file may be used under the terms of the + * GNU General Public License (the "GPL"), in which case the provisions of the GPL are + * applicable instead of those above. If you wish to allow use of your version of this + * file only under the terms of the GPL and not to allow others to use your version + * of this file under the MPL, indicate your decision by deleting the provisions above + * and replace them with the notice and other provisions required by the GPL License. + * If you do not delete the provisions above, a recipient may use your version of + * this file under either the MPL or the GPL. + */ + +package com.moparisthebest.hl7; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class Message { + + public static final Encoding DEFAULT_ENCODING = new Encoding('\r', '~', '|', '^', '&', '\\'); // http://healthstandards.com/blog/2006/11/02/hl7-escape-sequences/ + public static final Encoding NEWLINE_ENCODING = new Encoding('\n', '~', '|', '^', '&', '\\'); + + private final List lines = new ArrayList<>(); + + public Message() { + line("MSH"); + } + + public Message(final String sendingApplication, final String sendingFacility, final String receivingApplication, final String receivingFacility, final String messageCode, final String messageTriggerEvent, final String messageControlId, final String processingId, final String version) { + final Line msh = line("MSH"); + msh.field(3).value(sendingApplication); + msh.field(4).value(sendingFacility); + msh.field(5).value(receivingApplication); + msh.field(6).value(receivingFacility); + msh.field(7).date(Instant.now()); + + final Field messageType = msh.field(9); + messageType.component(1).value(messageCode); + messageType.component(2).value(messageTriggerEvent); + messageType.component(3).value(messageCode + "_" + messageTriggerEvent); + + msh.field(10).value(messageControlId); + msh.field(11).value(processingId); + msh.field(12).value(version); + } + + public Message(final String s) throws ParseException { + final int mshIdx = s.indexOf("MSH"); + if(mshIdx == -1 || s.length() < (mshIdx + 8)) + throw new ParseException("HL7 messages must start with an MSH segment", 0); + + // detect segmentDelimiter + char segmentDelimiter = DEFAULT_ENCODING.segmentDelimiter; + final char fieldDelimiter = s.charAt(mshIdx + 3); + byte fieldDelimiterCount = 0; + // 11 fields in is the version, next non-number or . is segmentDelimiter + for(int i = mshIdx; i < s.length(); ++i) { + if(fieldDelimiterCount < 11) { + if(s.charAt(i) == fieldDelimiter) + ++fieldDelimiterCount; + } else { + final char c = s.charAt(i); + if(c != '.' && !Character.isDigit(c)) { + segmentDelimiter = c; + break; + } + } + } + + final Encoding enc = new Encoding(segmentDelimiter, s.charAt(mshIdx + 5), fieldDelimiter, s.charAt(mshIdx + 4), s.charAt(mshIdx + 7), s.charAt(mshIdx + 6)); + for(final String line : enc.splitSegment("MSH" + fieldDelimiter + s.substring(mshIdx + 8))) + if(!line.isEmpty()) + lines.add(new Line(line, enc)); + // shift MSH one to account for brain-dead 1st field while setting encoding + final Line msh = this.lines.get(0); + msh.field(2); // expand array sufficiently + msh.subComponents.add(1, enc.msh1); + msh.subComponents.set(2, enc); + } + + public String encode() { + return this.encode(DEFAULT_ENCODING); + } + + public String encode(final Encoding enc) { + // set encoding + final Line msh = this.lines.get(0); + msh.field(2); // expand array sufficiently + msh.subComponents.set(1, enc.msh1); + msh.subComponents.set(2, enc); + + final StringBuilder sb = new StringBuilder(); + for(final Line line : lines) + line.encode(sb, enc); + if(!lines.isEmpty()){ + int index = 1; + while (index > 0 && sb.charAt(index = sb.length() - 1) == enc.segmentDelimiter) + sb.setLength(index); + } + return sb.toString(); + } + + public Line optionalLine(final String type) { + for(final Line line : lines) + if(line.field(0).equals(type)) + return line; + return null; + } + + public Line line(final String type) { + Line line = optionalLine(type); + if(line == null) + lines.add(line = new Line(type)); + return line; + } + + public Line line(final int index) { + return lines.get(index); + } + + public List getLines() { + return lines; + } + + public List lines(final String type) { + return lines.stream().filter(l -> l.field(0).equals(type)).collect(Collectors.toList()); + } + + public Line add(final String line) { + final Line ret = new Line(line); + lines.add(ret); + return ret; + } + + public Message add(final Line line) { + lines.add(line); + return this; + } + + public Message writeAndRead(final Socket sock, final Encoding enc) throws IOException, ParseException { + return this.writeAndRead(sock.getOutputStream(), sock.getInputStream(), enc); + } + + public Message writeAndRead(final OutputStream os, final InputStream is, final Encoding enc) throws IOException, ParseException { + this.writeTo(os, enc); + os.flush(); + return readFrom(is, enc); + } + + public void writeTo(final OutputStream os, final Encoding enc) throws IOException { + os.write(enc.verticalTab); + // todo: this can be made dramatically faster by writing to this directly instead of a StringBuilder + os.write(this.encode(enc).getBytes(StandardCharsets.US_ASCII)); + os.write(enc.fileSeperator); + os.write(enc.segmentDelimiter); + } + + public static Message readFrom(final InputStream is, final Encoding enc) throws IOException, ParseException { + final StringBuilder sb = new StringBuilder(); + for (int i = -1; (i = is.read()) != -1; ) { + //System.out.println("i: "+i); + final char c = (char) i; + //System.out.println("c: "+c); + if (c == enc.fileSeperator) { // end of line/message + // next char should be segmentDelimiter + i = is.read(); + if (((char) i) != enc.segmentDelimiter) + throw new ParseException("HL7 messages have a segmentDelimiter directly following a fileSeperator", sb.length()); + return new Message(sb.toString()); + } else { + sb.append(c); + } + } + return null; // no complete message received + } + + public Message writeAndRead(final Socket sock) throws IOException, ParseException { + return this.writeAndRead(sock, DEFAULT_ENCODING); + } + + public Message writeAndRead(final OutputStream os, final InputStream is) throws IOException, ParseException { + return this.writeAndRead(os, is, DEFAULT_ENCODING); + } + + public void writeTo(final OutputStream os) throws IOException { + this.writeTo(os, DEFAULT_ENCODING); + } + + public static Message readFrom(final InputStream is) throws IOException, ParseException { + return readFrom(is, DEFAULT_ENCODING); + } + + @Override + public String toString() { + return "Message" + lines; + } +} diff --git a/src/main/java/com/moparisthebest/hl7/Repetition.java b/src/main/java/com/moparisthebest/hl7/Repetition.java new file mode 100644 index 0000000..72e7f3e --- /dev/null +++ b/src/main/java/com/moparisthebest/hl7/Repetition.java @@ -0,0 +1,78 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.mozilla.org/MPL/ + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the + * specific language governing rights and limitations under the License. + * + * Copyright (c) 2016 Travis Burtrum. + * + * Alternatively, the contents of this file may be used under the terms of the + * GNU General Public License (the "GPL"), in which case the provisions of the GPL are + * applicable instead of those above. If you wish to allow use of your version of this + * file only under the terms of the GPL and not to allow others to use your version + * of this file under the MPL, indicate your decision by deleting the provisions above + * and replace them with the notice and other provisions required by the GPL License. + * If you do not delete the provisions above, a recipient may use your version of + * this file under either the MPL or the GPL. + */ + +package com.moparisthebest.hl7; + +public class Repetition extends SubComponentContainer { + + Repetition() { + super(); + } + + Repetition(final String s) { + super(s); + } + + Repetition(final String s, final Encoding enc) { + super(s, enc, enc.componentDelimiter); + } + + @Override + Component newSubComponent() { + return new Component(); + } + + @Override + Component newSubComponent(final String s) { + return new Component(s); + } + + @Override + Component newSubComponent(final String s, final Encoding enc) { + return new Component(s, enc); + } + + public void encode(final StringBuilder sb, final Encoding enc) { + super.encode(sb, enc, enc.componentDelimiter, enc.repetitionDelimiter); + } + + public Component component(final int index) { + return super.subComponent1Based(index); + } + + public SubComponent subComponent(final int componentIndex, final int subComponentIndex) { + return this.component(componentIndex).subComponent(subComponentIndex); + } + + // shorter aliases + + public Component c(final int componentIndex) { + return this.component(componentIndex); + } + + public SubComponent sc(final int componentIndex, final int subComponentIndex) { + return this.subComponent(componentIndex, subComponentIndex); + } + + @Override + public String toString() { + return super.toString("Component"); + } +} diff --git a/src/main/java/com/moparisthebest/hl7/RepetitionCounter.java b/src/main/java/com/moparisthebest/hl7/RepetitionCounter.java new file mode 100644 index 0000000..11f7c5d --- /dev/null +++ b/src/main/java/com/moparisthebest/hl7/RepetitionCounter.java @@ -0,0 +1,50 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.mozilla.org/MPL/ + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the + * specific language governing rights and limitations under the License. + * + * Copyright (c) 2016 Travis Burtrum. + * + * Alternatively, the contents of this file may be used under the terms of the + * GNU General Public License (the "GPL"), in which case the provisions of the GPL are + * applicable instead of those above. If you wish to allow use of your version of this + * file only under the terms of the GPL and not to allow others to use your version + * of this file under the MPL, indicate your decision by deleting the provisions above + * and replace them with the notice and other provisions required by the GPL License. + * If you do not delete the provisions above, a recipient may use your version of + * this file under either the MPL or the GPL. + */ + +package com.moparisthebest.hl7; + +public class RepetitionCounter { + + private final Field field; + private int index; + + RepetitionCounter(final Field field, final int index) { + this.field = field; + this.index = index - 1; + } + + public Repetition nextRepetition() { + return field.r(++index); + } + + public Repetition currentRepetition() { + return field.r(index); + } + + // shorter aliases + + public Repetition nr() { + return nextRepetition(); + } + + public Repetition cr() { + return currentRepetition(); + } +} diff --git a/src/main/java/com/moparisthebest/hl7/SubComponent.java b/src/main/java/com/moparisthebest/hl7/SubComponent.java new file mode 100644 index 0000000..a398241 --- /dev/null +++ b/src/main/java/com/moparisthebest/hl7/SubComponent.java @@ -0,0 +1,115 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.mozilla.org/MPL/ + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the + * specific language governing rights and limitations under the License. + * + * Copyright (c) 2016 Travis Burtrum. + * + * Alternatively, the contents of this file may be used under the terms of the + * GNU General Public License (the "GPL"), in which case the provisions of the GPL are + * applicable instead of those above. If you wish to allow use of your version of this + * file only under the terms of the GPL and not to allow others to use your version + * of this file under the MPL, indicate your decision by deleting the provisions above + * and replace them with the notice and other provisions required by the GPL License. + * If you do not delete the provisions above, a recipient may use your version of + * this file under either the MPL or the GPL. + */ + +package com.moparisthebest.hl7; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.ResolverStyle; +import java.time.format.SignStyle; +import java.util.Locale; + +import static java.time.temporal.ChronoField.*; + +public interface SubComponent { + + DateTimeFormatter LOCAL_DATE = new DateTimeFormatterBuilder() + .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) + .appendValue(MONTH_OF_YEAR, 2) + .appendValue(DAY_OF_MONTH, 2) + .parseStrict() + .toFormatter(Locale.US) + .withResolverStyle(ResolverStyle.STRICT); + + DateTimeFormatter LOCAL_DATE_TIME = new DateTimeFormatterBuilder() + .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) + .appendValue(MONTH_OF_YEAR, 2) + .appendValue(DAY_OF_MONTH, 2) + .appendValue(HOUR_OF_DAY, 2) + .appendValue(MINUTE_OF_HOUR, 2) + .parseStrict() + .toFormatter(Locale.US) + .withResolverStyle(ResolverStyle.STRICT); + + DateTimeFormatter INSTANT = new DateTimeFormatterBuilder() + .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD) + .appendValue(MONTH_OF_YEAR, 2) + .appendValue(DAY_OF_MONTH, 2) + .appendValue(HOUR_OF_DAY, 2) + .appendValue(MINUTE_OF_HOUR, 2) + .appendValue(SECOND_OF_MINUTE, 2) + .appendLiteral('.') + .appendValue(MILLI_OF_SECOND, 3) + .appendOffset("+HHMM", "0000") + .parseStrict() + .toFormatter(Locale.US) + .withZone(ZoneId.systemDefault()) + .withResolverStyle(ResolverStyle.STRICT); + + SubComponent value(final String value); + + String value(); + + boolean equals(final String s); + + void encode(final StringBuilder sb, final Encoding enc); + + default boolean isEmpty() { + return this.equals(""); + } + + default void date(final LocalDate date) { + this.value(LOCAL_DATE.format(date)); + } + + default void date(final LocalDateTime date) { + this.value(LOCAL_DATE_TIME.format(date)); + } + + default void date(final Instant date) { + this.value(INSTANT.format(date)); + } + + // shorter aliases + + default SubComponent v(final String value) { + return this.value(value); + } + + default String v() { + return this.value(); + } + + default void d(final LocalDate date) { + this.date(date); + } + + default void d(final LocalDateTime date) { + this.date(date); + } + + default void d(final Instant date) { + this.date(date); + } +} diff --git a/src/main/java/com/moparisthebest/hl7/SubComponentContainer.java b/src/main/java/com/moparisthebest/hl7/SubComponentContainer.java new file mode 100644 index 0000000..defd356 --- /dev/null +++ b/src/main/java/com/moparisthebest/hl7/SubComponentContainer.java @@ -0,0 +1,117 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.mozilla.org/MPL/ + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the + * specific language governing rights and limitations under the License. + * + * Copyright (c) 2016 Travis Burtrum. + * + * Alternatively, the contents of this file may be used under the terms of the + * GNU General Public License (the "GPL"), in which case the provisions of the GPL are + * applicable instead of those above. If you wish to allow use of your version of this + * file only under the terms of the GPL and not to allow others to use your version + * of this file under the MPL, indicate your decision by deleting the provisions above + * and replace them with the notice and other provisions required by the GPL License. + * If you do not delete the provisions above, a recipient may use your version of + * this file under either the MPL or the GPL. + */ + +package com.moparisthebest.hl7; + +import java.util.ArrayList; +import java.util.List; + +abstract class SubComponentContainer implements SubComponent { + + protected final List subComponents = new ArrayList<>(); + + protected SubComponentContainer() { + subComponents.add(newSubComponent()); + } + + protected SubComponentContainer(final String s) { + subComponents.add(newSubComponent(s)); + } + + protected SubComponentContainer(final String s, final Encoding enc, final char thisDelimiter) { + for(final String line : enc.split(s, thisDelimiter)) + subComponents.add(newSubComponent(line, enc)); + if(subComponents.isEmpty()) + subComponents.add(newSubComponent()); + } + + abstract T newSubComponent(); + abstract T newSubComponent(final String s); + abstract T newSubComponent(final String s, final Encoding enc); + + protected final void encode(final StringBuilder sb, final Encoding enc, final char thisDelimiter, final char parentDelimiter) { + for(final SubComponent e : subComponents) + e.encode(sb, enc); + if(!subComponents.isEmpty()){ + int index = 1, count = -1; + while (++count < subComponents.size() && index > 0 && sb.charAt(index = sb.length() - 1) == thisDelimiter) + sb.setLength(index); + } + sb.append(parentDelimiter); + } + + protected T subComponent0Based(final int index) { + while(subComponents.size() <= index) + subComponents.add(newSubComponent()); + return subComponents.get(index); + } + + protected T subComponent1Based(final int index) { + while(subComponents.size() < index) + subComponents.add(newSubComponent()); + return subComponents.get(index - 1); + } + + @Override + public SubComponent value(final String value) { + if(subComponents.size() == 1) { + subComponents.get(0).value(value); + } else { + subComponents.clear(); + subComponents.add(newSubComponent(value)); + } + return this; + } + + @Override + public String value() { + // todo: this isn't ALWAYS correct, it just returns the first, ignoring any other field/repetition/component/subcomponent/whatever + return subComponents.isEmpty() ? "" : subComponents.get(0).value(); + } + + @Override + public boolean equals(final String s) { + // more complicated than you'd think + if("".equals(s)) { + // special case for empty string + if(subComponents.isEmpty()) + return true; + // otherwise every one needs to be empty + for(final SubComponent sc : subComponents) + if(!sc.isEmpty()) + return false; + } else { + if(subComponents.isEmpty()) + return false; + boolean first = true; + // first has to match, and every OTHER one needs to be empty now... + for(final SubComponent sc : subComponents) { + if (!(first ? sc.equals(s) : sc.isEmpty())) + return false; + first = false; + } + } + return true; + } + + protected final String toString(final String name) { + return subComponents.size() == 1 ? subComponents.get(0).toString() : name + subComponents; + } +} diff --git a/src/main/java/com/moparisthebest/hl7/SubComponentImpl.java b/src/main/java/com/moparisthebest/hl7/SubComponentImpl.java new file mode 100644 index 0000000..a9c9158 --- /dev/null +++ b/src/main/java/com/moparisthebest/hl7/SubComponentImpl.java @@ -0,0 +1,70 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.mozilla.org/MPL/ + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the + * specific language governing rights and limitations under the License. + * + * Copyright (c) 2016 Travis Burtrum. + * + * Alternatively, the contents of this file may be used under the terms of the + * GNU General Public License (the "GPL"), in which case the provisions of the GPL are + * applicable instead of those above. If you wish to allow use of your version of this + * file only under the terms of the GPL and not to allow others to use your version + * of this file under the MPL, indicate your decision by deleting the provisions above + * and replace them with the notice and other provisions required by the GPL License. + * If you do not delete the provisions above, a recipient may use your version of + * this file under either the MPL or the GPL. + */ + +package com.moparisthebest.hl7; + +import java.util.Objects; + +class SubComponentImpl implements SubComponent { + + protected String value; + + SubComponentImpl() { + this(""); + } + + SubComponentImpl(final String value) { + value(value); + } + + SubComponentImpl(final String s, final Encoding enc) { + this.value = enc.decode(s); + } + + public void encode(final StringBuilder sb, final Encoding enc) { + sb.append(enc.encode(this.value)); + sb.append(enc.subComponentDelimiter); + } + + String encode(final Encoding enc) { + return enc.encode(this.value); + } + + @Override + public SubComponent value(final String value) { + this.value = value == null ? "" : value; + return this; + } + + @Override + public boolean equals(final String s) { + return Objects.equals(value, s); + } + + @Override + public String value() { + return value; + } + + @Override + public String toString() { + return value; + } +}