All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.uhn.hl7v2.util.ParseTester Maven / Gradle / Ivy

There is a newer version: 2.3
Show newest version
package ca.uhn.hl7v2.util;

import ca.uhn.hl7v2.parser.*;
import ca.uhn.hl7v2.HL7Exception;
import ca.uhn.hl7v2.model.Message;
import java.io.*;
import java.util.ArrayList;

/**
 * Tests correctness of message parsing by testing equivalence of re-encoded
 * form with original.
 * @author Bryan Tripp
 */
public class ParseTester {
    
    private static GenericParser parser = new GenericParser();
    private BufferedReader source;
    private String context;
    
    /** Creates a new instance of ParseTester */
    public ParseTester() {
    }
    
    /**
     * Checks whether the given message parses correctly with a GenericParser.
     * Failure indicates that the parsed and re-encoded message is semantically
     * different than the original, or that the message could not be parsed.  This 
     * may stem from an error in the parser, or from an error in the message.  This 
     * may also arise from unexpected message components (e.g. Z-segments) although 
     * in future HAPI versions these will be parsed as well.
     * @param message an XML or ER7 encoded message string
     * @return null if it parses correctly, an HL7Exception otherwise
     */
    public static HL7Exception parsesCorrectly(String context, String message) {
        HL7Exception problem = null;
        try {
            Message m = parser.parse(message);
            String encoding = parser.getEncoding(message);
            String result = parser.encode(m, encoding);
            if (!EncodedMessageComparator.equivalent(message, result)) {
                problem = new HL7Exception(context + ": Original differs semantically from parsed/encoded message.\r\n-----Original:------------\r\n" 
                    + message + " \r\n------ Parsed/Encoded: ----------\r\n" + result + " \r\n-----Original Standardized: ---------\r\n"
                    + EncodedMessageComparator.standardize(message) + " \r\n---------------------\r\n");
            }            
        } catch (Exception e) {
            problem = new HL7Exception(context + ": " + e.getMessage() + " in message: \r\n-------------\r\n" + message + "\r\n-------------");;
        }
        return problem; 
    }
    
    /**
     * Sets the source of message data (messages must be delimited by blank lines)
     */
    public void setSource(Reader source) {
        this.source = new BufferedReader(new CommentFilterReader(source));
    }
    
    /**
     * Sets a description of the context of the messages (e.g. file name) that can be 
     * reported within error messages.  
     */
    public void setContext(String description) {
        this.context = description;
    }
    
    /**
     * Sets the source reader to point to the given file, and tests
     * all the messages therein (if a directory, processes all contained
     * files recursively).
     */
    public HL7Exception[] testAll(File source) throws IOException {
        ArrayList list = new ArrayList();
        System.out.println("Testing " + source.getPath());
        if (source.isDirectory()) {
            File[] contents = source.listFiles();
            for (int i = 0; i < contents.length; i++) {
                HL7Exception[] exceptions = testAll(contents[i]);
                for (int j = 0; j < exceptions.length; j++) {
                    list.add(exceptions[j]);
                }
            }
        } else if (source.isFile()) {          
            FileReader in = new FileReader(source);
            setSource(in);
            setContext(source.getAbsolutePath());
            HL7Exception[] exceptions = testAll();
            for (int i = 0; i < exceptions.length; i++) {
                list.add(exceptions[i]);
            }
        } else {
            System.out.println("Warning: " + source.getPath() + " is not a normal file");
        }
        return (HL7Exception[]) list.toArray(new HL7Exception[0]);
    }
    
    /**
     * Tests all remaining messages available from the currrent source.
     */
    public HL7Exception[] testAll() throws IOException {
        ArrayList list = new ArrayList();

        String message = null;
        while ((message = getNextMessage()).length() > 0) {
            HL7Exception e = parsesCorrectly(this.context, message);
            if (e != null) list.add(e);
        }
        
        return (HL7Exception[]) list.toArray(new HL7Exception[0]);
    }
    
    /**
     * Retrieves the next message (setSource() must be called first).  The next message
     * is interpreted as everything up to the next blank line, not including
     * C or C++ style comments (or blank lines themselves).  An empty string
     * indicates that there are no more messages.
     */
    public String getNextMessage() throws IOException {
        if (this.source == null) throw new IOException("Message source is null -- call setSource() first");
        
        StringBuffer message = new StringBuffer();
        boolean started = false; //got at least one non-blank line
        boolean finished = false; //got a blank line after started, or end of stream
        while (!finished) {
            String line = this.source.readLine();
            if (line == null || (started && line.trim().length() == 0)) {
                finished = true;
            } else {
                if (line.trim().length() > 0) {
                    started = true;
                    message.append(line);
                    message.append("\r");
                }
            }
        }
        if (message.toString().trim().length() == 0) {
            return "";
        } else {
            return message.toString(); // can't trim by default (will omit final end-segment)
        }
    }
    
    /**
     * Command line tool for testing messages in files.
     */
    public static void main(String args[]) {
        if (args.length != 1
        || args[0].equalsIgnoreCase("-?")
        || args[0].equalsIgnoreCase("-h")
        || args[0].equalsIgnoreCase("-help")) {
            System.out.println("USAGE:");
            System.out.println("  ParseTester ");
            System.out.println();
            System.out.println("   must be either a file containing HL7 messages or a directory containing such files");
            System.out.println();
            System.out.println("Notes:");
            System.out.println(" - Messages can be XML or ER7 encoded. ");
            System.out.println(" - If there are multiple messages in a file they must be delimited by blank lines");
            System.out.println(" - C and C++ style comments are skipped");
            
        } else {
            try {                
                System.out.println("Testing ... ");
                File source = new File(args[0]);
                ParseTester tester = new ParseTester();
                HL7Exception[] exceptions = tester.testAll(source);
                if (exceptions.length > 0) System.out.println("Parsing problems with tested messages: ");
                for (int i = 0; i < exceptions.length; i++) {
                    System.out.println("PROBLEM #" + (i+1));
                    System.out.println(exceptions[i].getMessage());
                }
            } catch (IOException e) {
                System.out.println("Testing failed to complete because of a problem reading source file(s) ... \r\n");
                e.printStackTrace();
            }
        }
    }
    
    /**
     * Removes C and C++ style comments from a reader stream.  C style comments are
     * distinguished from URL protocol delimiters by the preceding colon in the
     * latter.
     */
    public static class CommentFilterReader extends PushbackReader {
        
        private final char[] startCPPComment = {'/', '*'};
        private final char[] endCPPComment = {'*', '/'};
        private final char[] startCComment = {'/', '/'};
        private final char[] endCComment = {'\n'};
        private final char[] protocolDelim = {':', '/', '/'};
        
        public CommentFilterReader(Reader in) {
            super(in, 5);
        }
        
        /**
         * Returns the next character, not including comments.
         */
        public int read() throws IOException {
            if (atSequence(protocolDelim)) {
                //proceed normally
            } else if (atSequence(startCPPComment)) {
                //skip() doesn't seem to work for some reason
                while (!atSequence(endCPPComment)) super.read();
                for (int i = 0; i < endCPPComment.length; i++) super.read();
            } else if (atSequence(startCComment)) {
                while (!atSequence(endCComment)) super.read();
                for (int i = 0; i < endCComment.length; i++) super.read();
            }
            return super.read();            
        }
                
        public int read(char[] cbuf, int off, int len) throws IOException {
            int i = -1;
            boolean done = false;
            while (++i < len) {
                int next = read();
                if (next == 65535 || next == -1) { //Pushback causes -1 to convert to 65535
                    done = true;
                    break;  
                }
                cbuf[off + i] = (char) next;
            }
            if (i == 0 && done) i = -1; 
            return i; 
        }            
        
        /**
         * Tests incoming data for match with char sequence, resets reader when done.
         */
        private boolean atSequence(char[] sequence) throws IOException {
            boolean result = true;
            int i = -1;
            int[] data = new int[sequence.length];
            while (++i < sequence.length && result == true) {
                data[i] = super.read();
                if ((char) data[i] != sequence[i]) result = false; //includes case where end of stream reached
            }
            for (int j = i-1; j >= 0; j--) {
                this.unread(data[j]);
            }
            return result;
        }        
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy