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}