View Javadoc
1   package ca.uhn.hl7v2.util;
2   
3   import java.io.IOException;
4   import java.io.InputStream;
5   import java.io.InputStreamReader;
6   import java.io.PushbackReader;
7   import java.io.Reader;
8   import java.util.Iterator;
9   
10  import org.slf4j.Logger;
11  import org.slf4j.LoggerFactory;
12  
13  /**
14   * <p>
15   * Reads from an {@link InputStream} containing a stream of encoded HL7 messages
16   * and iterates over those messages. This class is geared towards reading from
17   * files, and tries to be very lenient about the format of the stream,
18   * specifically concerning control characters and line endings. It should be
19   * safe to provide a stream containing Windows or Unix line endings (which will
20   * be treated as segment delimiters). It is also safe to provide a stream
21   * containing MLLP control blocks before and after each message (although these
22   * will not be validated! Do not use this class to read MLLP messages from a
23   * socket stream!)
24   * </p>
25   * <p>
26   * The input stream could, for example, be a FileInputStream reading from a text
27   * file containing a number of HL7 messages in plain text format.
28   * </p>
29   * <p>
30   * Usage note: If an IOException occurs while reading from the stream or a
31   * message parsing exception occurs, it will be thrown as an unchecked
32   * {@link ParseFailureError}
33   * </p>
34   */
35  public class Hl7InputStreamMessageStringIterator implements Iterator<String> {
36  
37  	@SuppressWarnings("unused")
38  	private static final Logger ourLog = LoggerFactory.getLogger(Hl7InputStreamMessageStringIterator.class);
39  	
40  	private final StringBuilder myBuffer = new StringBuilder();
41  	private boolean myFoundMessageInBuffer = false;
42  	private Boolean myHasNext;
43  	private boolean myIgnoreComments;
44  	private String myNext;
45  	private final Reader myReader;
46  
47  	/**
48  	 * Constructor
49  	 * 
50  	 * @param theInputStream
51  	 *            The input stream to read from
52  	 */
53  	public Hl7InputStreamMessageStringIterator(InputStream theInputStream) {
54  		this(new InputStreamReader(theInputStream));
55  	}
56  
57  	/**
58  	 * Constructor
59  	 * 
60  	 * @param theReader
61  	 *            The reader to read from
62  	 */
63  	public Hl7InputStreamMessageStringIterator(Reader theReader) {
64  		myReader = new PushbackReader(theReader);
65  	}
66  
67  	/**
68  	 * {@inheritDoc}
69  	 */
70  	public boolean hasNext() {
71  		if (myHasNext == null) {
72  
73  			int next;
74  			int prev = -1;
75  			int endOfBuffer = -1;
76  			boolean inComment = false;
77  			
78  			while (true) {
79  				try {
80  					next = myReader.read();
81  				} catch (IOException e) {
82  					throw new ParseFailureError("IOException reading from input", e);
83  				}
84  
85  				if (next == -1) {
86  					break;
87  				}
88  
89  				char nextChar = (char) next;
90  				if (nextChar == '#' && myIgnoreComments && (prev == -1 || prev == '\n' || prev == '\r')) {
91  					inComment = true;
92  					continue;
93  				}
94  				
95  				// Convert '\n' or "\r\n" to '\r'
96  				if (nextChar == 10) {
97  					if (myBuffer.length() > 0) {
98  						if (myBuffer.charAt(myBuffer.length() - 1) == 13) {
99  							// don't append
100 						} else {
101 							myBuffer.append((char) 13);
102 						}
103 					}
104 				} else if (inComment) {
105 					if (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 false;
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 false;
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 }