View Javadoc

1   package ca.uhn.hl7v2.util;
2   
3   import ca.uhn.hl7v2.parser.*;
4   import ca.uhn.hl7v2.HL7Exception;
5   import ca.uhn.hl7v2.model.Message;
6   import java.io.*;
7   import java.util.ArrayList;
8   import java.util.Arrays;
9   import java.util.List;
10  
11  /**
12   * Tests correctness of message parsing by testing equivalence of re-encoded
13   * form with original.
14   * @author Bryan Tripp
15   * @deprecated
16   */
17  public class ParseTester {
18      
19      private static GenericParser parser = new GenericParser();
20      private BufferedReader source;
21      private String context;
22      
23      /** Creates a new instance of ParseTester */
24      public ParseTester() {
25      }
26      
27      /**
28       * Checks whether the given message parses correctly with a GenericParser.
29       * Failure indicates that the parsed and re-encoded message is semantically
30       * different than the original, or that the message could not be parsed.  This 
31       * may stem from an error in the parser, or from an error in the message.  This 
32       * may also arise from unexpected message components (e.g. Z-segments) although 
33       * in future HAPI versions these will be parsed as well.
34       * @param message an XML or ER7 encoded message string
35       * @return null if it parses correctly, an HL7Exception otherwise
36       */
37      public static HL7Exception parsesCorrectly(String context, String message) {
38          HL7Exception problem = null;
39          try {
40              Message m = parser.parse(message);
41              String encoding = parser.getEncoding(message);
42              String result = parser.encode(m, encoding);
43              if (!EncodedMessageComparator.equivalent(message, result)) {
44                  problem = new HL7Exception(context + ": Original differs semantically from parsed/encoded message.\r\n-----Original:------------\r\n" 
45                      + message + " \r\n------ Parsed/Encoded: ----------\r\n" + result + " \r\n-----Original Standardized: ---------\r\n"
46                      + EncodedMessageComparator.standardize(message) + " \r\n---------------------\r\n");
47              }            
48          } catch (Exception e) {
49              problem = new HL7Exception(context + ": " + e.getMessage() + " in message: \r\n-------------\r\n" + message + "\r\n-------------");;
50          }
51          return problem; 
52      }
53      
54      /**
55       * Sets the source of message data (messages must be delimited by blank lines)
56       */
57      public void setSource(Reader source) {
58          this.source = new BufferedReader(new CommentFilterReader(source));
59      }
60      
61      /**
62       * Sets a description of the context of the messages (e.g. file name) that can be 
63       * reported within error messages.  
64       */
65      public void setContext(String description) {
66          this.context = description;
67      }
68      
69      /**
70       * Sets the source reader to point to the given file, and tests
71       * all the messages therein (if a directory, processes all contained
72       * files recursively).
73       */
74      public HL7Exception[] testAll(File source) throws IOException {
75          List<HL7Exception> list = new ArrayList<HL7Exception>();
76          System.out.println("Testing " + source.getPath());
77          if (source.isDirectory()) {
78              File[] contents = source.listFiles();
79              for (int i = 0; i < contents.length; i++) {
80                  HL7Exception[] exceptions = testAll(contents[i]);
81                  list.addAll(Arrays.asList(exceptions));
82              }
83          } else if (source.isFile()) {          
84              FileReader in = new FileReader(source);
85              setSource(in);
86              setContext(source.getAbsolutePath());
87              HL7Exception[] exceptions = testAll();
88              list.addAll(Arrays.asList(exceptions));
89          } else {
90              System.out.println("Warning: " + source.getPath() + " is not a normal file");
91          }
92          return list.toArray(new HL7Exception[0]);
93      }
94      
95      /**
96       * Tests all remaining messages available from the currrent source.
97       */
98      public HL7Exception[] testAll() throws IOException {
99      	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 }