001package ca.uhn.hl7v2.util; 002 003import ca.uhn.hl7v2.parser.*; 004import ca.uhn.hl7v2.HL7Exception; 005import ca.uhn.hl7v2.model.Message; 006import java.io.*; 007import java.util.ArrayList; 008import java.util.Arrays; 009import 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 */ 017public 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}