001 package ca.uhn.hl7v2.util;
002
003 import java.io.IOException;
004 import java.io.InputStream;
005 import java.io.InputStreamReader;
006 import java.io.PushbackReader;
007 import java.io.Reader;
008 import java.util.Iterator;
009
010 import org.slf4j.Logger;
011 import org.slf4j.LoggerFactory;
012
013 /**
014 * <p>
015 * Reads from an {@link InputStream} containing a stream of encoded HL7 messages
016 * and iterates over those messages. This class is geared towards reading from
017 * files, and tries to be very lenient about the format of the stream,
018 * specifically concerning control characters and line endings. It should be
019 * safe to provide a stream containing Windows or Unix line endings (which will
020 * be treated as segment delimiters). It is also safe to provide a stream
021 * containing MLLP control blocks before and after each message (although these
022 * will not be validated! Do not use this class to read MLLP messages from a
023 * socket stream!)
024 * </p>
025 * <p>
026 * The input stream could, for example, be a FileInputStream reading from a text
027 * file containing a number of HL7 messages in plain text format.
028 * </p>
029 * <p>
030 * Usage note: If an IOException occurs while reading from the stream or a
031 * message parsing exception occurs, it will be thrown as an unchecked
032 * {@link ParseFailureError}
033 * </p>
034 */
035 public class Hl7InputStreamMessageStringIterator implements Iterator<String> {
036
037 @SuppressWarnings("unused")
038 private static final Logger ourLog = LoggerFactory.getLogger(Hl7InputStreamMessageStringIterator.class);
039
040 private StringBuilder myBuffer = new StringBuilder();
041 private boolean myFoundMessageInBuffer = false;
042 private Boolean myHasNext;
043 private boolean myIgnoreComments;
044 private String myNext;
045 private Reader myReader;
046
047 /**
048 * Constructor
049 *
050 * @param theInputStream
051 * The input stream to read from
052 */
053 public Hl7InputStreamMessageStringIterator(InputStream theInputStream) {
054 this(new InputStreamReader(theInputStream));
055 }
056
057 /**
058 * Constructor
059 *
060 * @param theReader
061 * The reader to read from
062 */
063 public Hl7InputStreamMessageStringIterator(Reader theReader) {
064 myReader = new PushbackReader(theReader);
065 }
066
067 /**
068 * {@inheritDoc}
069 */
070 public boolean hasNext() {
071 if (myHasNext == null) {
072
073 int next;
074 int prev = -1;
075 int endOfBuffer = -1;
076 boolean inComment = false;
077
078 while (true) {
079 try {
080 next = myReader.read();
081 } catch (IOException e) {
082 throw new ParseFailureError("IOException reading from input", e);
083 }
084
085 if (next == -1) {
086 break;
087 }
088
089 char nextChar = (char) next;
090 if (nextChar == '#' && myIgnoreComments && (prev == -1 || prev == '\n' || prev == '\r')) {
091 inComment = true;
092 continue;
093 }
094
095 // Convert '\n' or "\r\n" to '\r'
096 if (nextChar == 10) {
097 if (myBuffer.length() > 0) {
098 if (myBuffer.charAt(myBuffer.length() - 1) == 13) {
099 // don't append
100 } else {
101 myBuffer.append((char) 13);
102 }
103 }
104 } else if (inComment) {
105 if (nextChar == 10 || nextChar == 13) {
106 inComment = false;
107 }
108 } else {
109 myBuffer.append(nextChar);
110 }
111
112 prev = next;
113
114 int bLength = myBuffer.length();
115 if (nextChar == 'H' && bLength >= 3) {
116 if (myBuffer.charAt(bLength - 2) == 'S') {
117 if (myBuffer.charAt(bLength - 3) == 'M') {
118 if (myFoundMessageInBuffer) {
119 if (myBuffer.charAt(bLength - 4) < 32) {
120 endOfBuffer = bLength - 3;
121 break;
122 }
123 } else {
124 // Delete any whitespace or other stuff before
125 // the first message
126 myBuffer.delete(0, bLength - 3);
127 myFoundMessageInBuffer = true;
128 }
129 }
130 }
131 }
132
133 } // while(true)
134
135 if (!myFoundMessageInBuffer) {
136 myHasNext = false;
137 return myHasNext;
138 }
139
140 String msgString;
141 if (endOfBuffer > -1) {
142 msgString = myBuffer.substring(0, endOfBuffer);
143 myBuffer.delete(0, endOfBuffer);
144 } else {
145 msgString = myBuffer.toString();
146 myBuffer.setLength(0);
147 }
148
149 if (!msgString.startsWith("MSH")) {
150 myHasNext = Boolean.FALSE;
151 return myHasNext;
152 }
153
154 myNext = msgString;
155 myHasNext = Boolean.TRUE;
156
157 }
158 return myHasNext;
159 }
160
161 /**
162 * {@inheritDoc}
163 */
164 public String next() {
165 if (!hasNext()) {
166 throw new IllegalStateException();
167 }
168 String retVal = myNext;
169 myNext = null;
170 myHasNext = null;
171 return retVal;
172 }
173
174 /**
175 * Unsupported method!
176 *
177 * @throws UnsupportedOperationException
178 * If called
179 */
180 public void remove() {
181 throw new UnsupportedOperationException();
182 }
183
184 /**
185 * If set to true, any lines beginning with a hash (#) will be ignored. This
186 * allows you to place comments in a file to be read if needed.
187 */
188 public void setIgnoreComments(boolean theIgnoreComments) {
189 myIgnoreComments = theIgnoreComments;
190 }
191
192 public static class ParseFailureError extends RuntimeException {
193
194 private static final long serialVersionUID = 1L;
195
196 public ParseFailureError(String theMessage, Exception theCause) {
197 super(theMessage, theCause);
198 }
199
200 }
201
202 }