001package ca.uhn.hl7v2.util; 002 003import java.io.IOException; 004import java.io.InputStream; 005import java.io.InputStreamReader; 006import java.io.PushbackReader; 007import java.io.Reader; 008import java.util.Iterator; 009 010import org.slf4j.Logger; 011import 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 */ 035public 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}