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 }