001    package ca.uhn.hl7v2.util;
002    
003    import ca.uhn.hl7v2.parser.*;
004    import ca.uhn.hl7v2.HL7Exception;
005    import ca.uhn.hl7v2.model.Message;
006    import java.io.*;
007    import java.util.ArrayList;
008    import java.util.Arrays;
009    import java.util.List;
010    
011    /**
012     * Tests correctness of message parsing by testing equivalence of re-encoded
013     * form with original.
014     * @author Bryan Tripp
015     * @deprecated
016     */
017    public class ParseTester {
018        
019        private static GenericParser parser = new GenericParser();
020        private BufferedReader source;
021        private String context;
022        
023        /** Creates a new instance of ParseTester */
024        public ParseTester() {
025        }
026        
027        /**
028         * Checks whether the given message parses correctly with a GenericParser.
029         * Failure indicates that the parsed and re-encoded message is semantically
030         * different than the original, or that the message could not be parsed.  This 
031         * may stem from an error in the parser, or from an error in the message.  This 
032         * may also arise from unexpected message components (e.g. Z-segments) although 
033         * in future HAPI versions these will be parsed as well.
034         * @param message an XML or ER7 encoded message string
035         * @return null if it parses correctly, an HL7Exception otherwise
036         */
037        public static HL7Exception parsesCorrectly(String context, String message) {
038            HL7Exception problem = null;
039            try {
040                Message m = parser.parse(message);
041                String encoding = parser.getEncoding(message);
042                String result = parser.encode(m, encoding);
043                if (!EncodedMessageComparator.equivalent(message, result)) {
044                    problem = new HL7Exception(context + ": Original differs semantically from parsed/encoded message.\r\n-----Original:------------\r\n" 
045                        + message + " \r\n------ Parsed/Encoded: ----------\r\n" + result + " \r\n-----Original Standardized: ---------\r\n"
046                        + EncodedMessageComparator.standardize(message) + " \r\n---------------------\r\n");
047                }            
048            } catch (Exception e) {
049                problem = new HL7Exception(context + ": " + e.getMessage() + " in message: \r\n-------------\r\n" + message + "\r\n-------------");;
050            }
051            return problem; 
052        }
053        
054        /**
055         * Sets the source of message data (messages must be delimited by blank lines)
056         */
057        public void setSource(Reader source) {
058            this.source = new BufferedReader(new CommentFilterReader(source));
059        }
060        
061        /**
062         * Sets a description of the context of the messages (e.g. file name) that can be 
063         * reported within error messages.  
064         */
065        public void setContext(String description) {
066            this.context = description;
067        }
068        
069        /**
070         * Sets the source reader to point to the given file, and tests
071         * all the messages therein (if a directory, processes all contained
072         * files recursively).
073         */
074        public HL7Exception[] testAll(File source) throws IOException {
075            List<HL7Exception> list = new ArrayList<HL7Exception>();
076            System.out.println("Testing " + source.getPath());
077            if (source.isDirectory()) {
078                File[] contents = source.listFiles();
079                for (int i = 0; i < contents.length; i++) {
080                    HL7Exception[] exceptions = testAll(contents[i]);
081                    list.addAll(Arrays.asList(exceptions));
082                }
083            } else if (source.isFile()) {          
084                FileReader in = new FileReader(source);
085                setSource(in);
086                setContext(source.getAbsolutePath());
087                HL7Exception[] exceptions = testAll();
088                list.addAll(Arrays.asList(exceptions));
089            } else {
090                System.out.println("Warning: " + source.getPath() + " is not a normal file");
091            }
092            return list.toArray(new HL7Exception[0]);
093        }
094        
095        /**
096         * Tests all remaining messages available from the currrent source.
097         */
098        public HL7Exception[] testAll() throws IOException {
099            List<HL7Exception> list = new ArrayList<HL7Exception>();
100    
101            String message = null;
102            while ((message = getNextMessage()).length() > 0) {
103                HL7Exception e = parsesCorrectly(this.context, message);
104                if (e != null) list.add(e);
105            }
106            
107            return list.toArray(new HL7Exception[0]);
108        }
109        
110        /**
111         * Retrieves the next message (setSource() must be called first).  The next message
112         * is interpreted as everything up to the next blank line, not including
113         * C or C++ style comments (or blank lines themselves).  An empty string
114         * indicates that there are no more messages.
115         */
116        public String getNextMessage() throws IOException {
117            if (this.source == null) throw new IOException("Message source is null -- call setSource() first");
118            
119            StringBuffer message = new StringBuffer();
120            boolean started = false; //got at least one non-blank line
121            boolean finished = false; //got a blank line after started, or end of stream
122            while (!finished) {
123                String line = this.source.readLine();
124                if (line == null || (started && line.trim().length() == 0)) {
125                    finished = true;
126                } else {
127                    if (line.trim().length() > 0) {
128                        started = true;
129                        message.append(line);
130                        message.append("\r");
131                    }
132                }
133            }
134            if (message.toString().trim().length() == 0) {
135                return "";
136            } else {
137                return message.toString(); // can't trim by default (will omit final end-segment)
138            }
139        }
140        
141        /**
142         * Command line tool for testing messages in files.
143         */
144        public static void main(String args[]) {
145            if (args.length != 1
146            || args[0].equalsIgnoreCase("-?")
147            || args[0].equalsIgnoreCase("-h")
148            || args[0].equalsIgnoreCase("-help")) {
149                System.out.println("USAGE:");
150                System.out.println("  ParseTester <source>");
151                System.out.println();
152                System.out.println("  <source> must be either a file containing HL7 messages or a directory containing such files");
153                System.out.println();
154                System.out.println("Notes:");
155                System.out.println(" - Messages can be XML or ER7 encoded. ");
156                System.out.println(" - If there are multiple messages in a file they must be delimited by blank lines");
157                System.out.println(" - C and C++ style comments are skipped");
158                
159            } else {
160                try {                
161                    System.out.println("Testing ... ");
162                    File source = new File(args[0]);
163                    ParseTester tester = new ParseTester();
164                    HL7Exception[] exceptions = tester.testAll(source);
165                    if (exceptions.length > 0) System.out.println("Parsing problems with tested messages: ");
166                    for (int i = 0; i < exceptions.length; i++) {
167                        System.out.println("PROBLEM #" + (i+1));
168                        System.out.println(exceptions[i].getMessage());
169                    }
170                } catch (IOException e) {
171                    System.out.println("Testing failed to complete because of a problem reading source file(s) ... \r\n");
172                    e.printStackTrace();
173                }
174            }
175        }
176        
177        /**
178         * Removes C and C++ style comments from a reader stream.  C style comments are
179         * distinguished from URL protocol delimiters by the preceding colon in the
180         * latter.
181         */
182        public static class CommentFilterReader extends PushbackReader {
183            
184            private final char[] startCPPComment = {'/', '*'};
185            private final char[] endCPPComment = {'*', '/'};
186            private final char[] startCComment = {'/', '/'};
187            private final char[] endCComment = {'\n'};
188            private final char[] protocolDelim = {':', '/', '/'};
189            
190            public CommentFilterReader(Reader in) {
191                super(in, 5);
192            }
193            
194            /**
195             * Returns the next character, not including comments.
196             */
197            public int read() throws IOException {
198                if (atSequence(protocolDelim)) {
199                    //proceed normally
200                } else if (atSequence(startCPPComment)) {
201                    //skip() doesn't seem to work for some reason
202                    while (!atSequence(endCPPComment)) super.read();
203                    for (int i = 0; i < endCPPComment.length; i++) super.read();
204                } else if (atSequence(startCComment)) {
205                    while (!atSequence(endCComment)) super.read();
206                    for (int i = 0; i < endCComment.length; i++) super.read();
207                }
208                return super.read();            
209            }
210                    
211            public int read(char[] cbuf, int off, int len) throws IOException {
212                int i = -1;
213                boolean done = false;
214                while (++i < len) {
215                    int next = read();
216                    if (next == 65535 || next == -1) { //Pushback causes -1 to convert to 65535
217                        done = true;
218                        break;  
219                    }
220                    cbuf[off + i] = (char) next;
221                }
222                if (i == 0 && done) i = -1; 
223                return i; 
224            }            
225            
226            /**
227             * Tests incoming data for match with char sequence, resets reader when done.
228             */
229            private boolean atSequence(char[] sequence) throws IOException {
230                boolean result = true;
231                int i = -1;
232                int[] data = new int[sequence.length];
233                while (++i < sequence.length && result == true) {
234                    data[i] = super.read();
235                    if ((char) data[i] != sequence[i]) result = false; //includes case where end of stream reached
236                }
237                for (int j = i-1; j >= 0; j--) {
238                    this.unread(data[j]);
239                }
240                return result;
241            }        
242        }
243    }