ca.uhn.hl7v2.testpanel.model.msg.Hl7V2MessageCollection.java Source code

Java tutorial

Introduction

Here is the source code for ca.uhn.hl7v2.testpanel.model.msg.Hl7V2MessageCollection.java

Source

/**
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
 * specific language governing rights and limitations under the License.
 *
 * The Original Code is ""  Description:
 * ""
 *
 * The Initial Developer of the Original Code is University Health Network. Copyright (C)
 * 2001.  All Rights Reserved.
 *
 * Contributor(s): ______________________________________.
 *
 * Alternatively, the contents of this file may be used under the terms of the
 * GNU General Public License (the  "GPL"), in which case the provisions of the GPL are
 * applicable instead of those above.  If you wish to allow use of your version of this
 * file only under the terms of the GPL and not to allow others to use your version
 * of this file under the MPL, indicate your decision by deleting  the provisions above
 * and replace  them with the notice and other provisions required by the GPL License.
 * If you do not delete the provisions above, a recipient may use your version of
 * this file under either the MPL or the GPL.
 */
package ca.uhn.hl7v2.testpanel.model.msg;

import static org.apache.commons.lang.StringUtils.*;

import java.awt.EventQueue;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.bind.JAXB;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ca.uhn.hl7v2.HL7Exception;
import ca.uhn.hl7v2.conf.ProfileException;
import ca.uhn.hl7v2.model.Message;
import ca.uhn.hl7v2.model.Segment;
import ca.uhn.hl7v2.parser.GenericParser;
import ca.uhn.hl7v2.testpanel.model.AbstractModelClass;
import ca.uhn.hl7v2.testpanel.model.UnknownMessage;
import ca.uhn.hl7v2.testpanel.model.conf.ProfileFileList;
import ca.uhn.hl7v2.testpanel.model.conf.ProfileGroup;
import ca.uhn.hl7v2.testpanel.ui.ShowEnum;
import ca.uhn.hl7v2.testpanel.util.ClassUtils;
import ca.uhn.hl7v2.testpanel.util.LineEndingsEnum;
import ca.uhn.hl7v2.testpanel.util.Range;
import ca.uhn.hl7v2.testpanel.util.SegmentAndComponentPath;
import ca.uhn.hl7v2.testpanel.xsd.Hl7V2EncodingTypeEnum;
import ca.uhn.hl7v2.validation.ValidationContext;
import ca.uhn.hl7v2.validation.impl.DefaultValidation;
import ca.uhn.hl7v2.validation.impl.ValidationContextImpl;

public class Hl7V2MessageCollection extends AbstractModelClass {
    private static final Pattern FIRSTLINE_COMMENT_PATTERN = Pattern.compile("^\\#\\s*(\\S.*)");
    private static final Logger ourLog = LoggerFactory.getLogger(Hl7V2MessageCollection.class);
    public static final String PARSED_MESSAGES_PROPERTY = Hl7V2MessageCollection.class.getName()
            + "PARSED_MESSAGES_PROPERTY";
    public static final String PROP_DESCRIPTION = Hl7V2MessageCollection.class.getName() + "DESCRIPTION";
    public static final String PROP_ENCODING = Hl7V2MessageCollection.class.getName() + "ENCODING";
    public static final String PROP_HIGHLITED_PATH = Hl7V2MessageCollection.class.getName() + "HIGHLITED_PATH";
    public static final String PROP_HIGHLITED_RANGE = Hl7V2MessageCollection.class.getName() + "HIGHLITED_RANGE";
    public static final String PROP_SAVE_FILENAME = Hl7V2MessageCollection.class.getName() + "SAVE_FILENAME";
    public static final String PROP_VALIDATIONCONTEXT_OR_PROFILE = Hl7V2MessageCollection.class.getName()
            + "VALIDATIONCONTEXT";
    public static final String SAVED_PROPERTY = AbstractMessage.class.getName() + "SAVED_PROPERTY";
    public static final String SOURCE_MESSAGE_PROPERTY = Hl7V2MessageCollection.class.getName()
            + "SOURCE_MESSAGE_PROPERTY";

    private Hl7V2EncodingTypeEnum myEncoding = Hl7V2EncodingTypeEnum.ER_7;
    private String myHighlitedPath;
    private Range myHighlitedRange;
    private String myId;
    private String myLastSendToInterfaceId;
    private String myMessageDescription;
    private List<Range> myMessageRanges = new ArrayList<Range>();
    private List<AbstractMessage<?>> myMessages = new ArrayList<AbstractMessage<?>>();
    private GenericParser myParser;
    private String myProfileId;
    private ProfileGroup myRuntimeProfile;
    private Charset mySaveCharset;
    private boolean mySaved;
    private String mySaveFileName;
    private long mySaveFileTimestamp;
    private LineEndingsEnum mySaveLineEndings;
    private int mySendNumberOfTimes = 1;
    private ShowEnum myShow;
    private String mySourceMessage;
    private boolean myStripSaveComments;
    private ValidationContext myValidationContext = new DefaultValidation();

    /**
     * Constructor
     */
    public Hl7V2MessageCollection() {
        myParser = new GenericParser();
        myParser.setValidationContext(new ValidationContextImpl());

        myId = UUID.randomUUID().toString();
    }

    public synchronized void addComment(String theComment) {
        String oldValue = mySourceMessage;

        ensureSourceMessage();

        StringBuilder b = new StringBuilder();
        if (myEncoding == Hl7V2EncodingTypeEnum.ER_7) {
            for (String next : theComment.split("\\n")) {
                if (StringUtils.isNotBlank(next)) {
                    b.append("#");
                    b.append(next);
                    b.append("\n");
                } else {
                    b.append("\n");
                }
            }

        } else {
            b.append("<!--");
            b.append(theComment);
            b.append("-->\n");
        }

        int originalLength = mySourceMessage.length();
        mySourceMessage += b.toString();

        Comment comment = new Comment();
        comment.setSourceMessage(theComment);
        myMessages.add(comment);

        ensureSourceMessage();

        myMessageRanges.add(new Range(originalLength, originalLength + b.length()));

        firePropertyChange(SOURCE_MESSAGE_PROPERTY, oldValue, mySourceMessage);
        firePropertyChange(PARSED_MESSAGES_PROPERTY, oldValue, mySourceMessage);
    }

    public synchronized void addMessage(AbstractMessage<?> theMessage) {
        Validate.notNull(theMessage);

        int newIndex = myMessages.size();
        myMessages.add(theMessage);

        ensureSourceMessage();

        myMessageRanges.add(new Range(mySourceMessage.length()));

        if (theMessage instanceof Hl7V2MessageBase) {
            updateSourceMessageBasedOnParsedMessage(newIndex, (Message) theMessage.getParsedMessage());
        }
    }

    public void clearHighlight() {
        Range oldValue = myHighlitedRange;
        myHighlitedRange = null;
        firePropertyChange(PROP_HIGHLITED_RANGE, oldValue, myHighlitedRange);

    }

    public int countMessagesOfType(Class<Hl7V2MessageBase> theClass) {
        int retVal = 0;
        for (AbstractMessage<?> next : getMessages()) {
            if (theClass.isAssignableFrom(next.getClass())) {
                retVal++;
            }
        }
        return retVal;
    }

    private synchronized void doSetSourceMessageEr7(String message) {
        String[] lines = message.split("\\r");

        StringBuilder buf = new StringBuilder();
        ParserState curState = ParserState.NONE;
        int curStart = 0;
        for (String nextLine : lines) {
            ParserState newState;
            if (nextLine.startsWith("#")) {
                newState = ParserState.COMMENT;
            } else if (nextLine.startsWith("MSH")) {
                newState = ParserState.AT_MSG_START;
            } else {
                if (nextLine.trim().length() > 0) {
                    if (curState == ParserState.IN_MSG && nextLine.matches("^[A-Z][A-Z0-9][A-Z0-9].*")) {
                        newState = ParserState.IN_MSG;
                    } else {
                        newState = ParserState.UNKNOWN;
                    }
                } else {
                    newState = ParserState.NONE;
                }
            }

            if (curState != newState) {
                AbstractMessage<?> msg = null;
                String bufToString = buf.toString();
                if (bufToString.length() > 0) {
                    switch (curState) {
                    case AT_MSG_START:
                    case IN_MSG:
                        try {

                            // Note: this code is duplicated below.. that should
                            // be cleaned
                            // up probably, but until then if this is modified,
                            // modify it below
                            // also
                            ourLog.info("Found ER7 message");
                            Hl7V2MessageBase hl7msg = new Hl7V2MessageEr7();
                            hl7msg.setIndexWithinCollection(myMessages.size());
                            hl7msg.setRuntimeProfile(myRuntimeProfile);
                            hl7msg.setSourceMessage(bufToString);
                            msg = hl7msg;

                        } catch (PropertyVetoException e) {
                            msg = new UnknownMessage(bufToString);
                        }
                        break;
                    case COMMENT:
                        msg = new Comment(bufToString);
                        break;
                    case UNKNOWN:
                        msg = new UnknownMessage(bufToString);
                        break;
                    }

                    if (msg != null) {
                        myMessages.add(msg);
                        myMessageRanges.add(new Range(curStart, curStart + bufToString.length()));
                    }
                }

                curStart = curStart + bufToString.length();
                buf.setLength(0);

                if (newState == ParserState.AT_MSG_START) {
                    curState = ParserState.IN_MSG;
                } else {
                    curState = newState;
                }
            }

            buf.append(nextLine).append('\r');

        }

        String bufToString = buf.toString();
        if (bufToString.trim().length() > 0) {
            AbstractMessage<?> msg = null;
            switch (curState) {
            case IN_MSG:
            case AT_MSG_START:
                try {

                    ourLog.info("Found ER7 message");
                    Hl7V2MessageBase hl7msg = new Hl7V2MessageEr7();
                    hl7msg.setIndexWithinCollection(myMessages.size());
                    hl7msg.setRuntimeProfile(myRuntimeProfile);
                    hl7msg.setSourceMessage(bufToString);
                    msg = hl7msg;

                } catch (PropertyVetoException e) {
                    msg = new UnknownMessage(bufToString);
                }
                break;
            case COMMENT:
                msg = new Comment(bufToString);
                break;
            case UNKNOWN:
                msg = new UnknownMessage(bufToString);
                break;
            }

            if (msg != null) {
                myMessages.add(msg);
                myMessageRanges.add(new Range(curStart, curStart + bufToString.length()));
            }

        }

        // List<String> b = new ArrayList<String>();
        // StringBuilder fullMsgBuilder = new StringBuilder();
        // int start = 0;
        // int end = 0;
        // int charIndex = 0;
        // boolean inMsg = false;
        // for (String line : lines) {
        //
        // if (line.startsWith("MSH")) {
        // addMessageIfAny(b, start, end);
        // b.clear();
        // start = charIndex;
        // inMsg = true;
        // }
        //
        // if (line.trim().length() == 0) {
        // if (inMsg) {
        // addMessageIfAny(b, start, end);
        // b.clear();
        // inMsg = false;
        // }
        // } else if (!line.matches("^[A-Z][A-Z0-9]{2}.*") && inMsg) {
        // addMessageIfAny(b, start, end);
        // b.clear();
        // inMsg = false;
        // }
        //
        // b.add(line);
        //
        // fullMsgBuilder.append(line);
        // fullMsgBuilder.append('\r');
        //
        // charIndex += line.length();
        // charIndex++;
        // end = charIndex;
        // }
        //
        // addMessageIfAny(b, start, end);
    }

    private synchronized void doSetSourceMessageXml(String theMessage) {
        int rangeStart = 0;
        int rangeEnd = 0;

        Pattern p = Pattern.compile("<([A-Za-z0-9_]+).*?>");
        Matcher m = p.matcher(theMessage);

        while (rangeStart < theMessage.length() && m.find(rangeStart)) {

            int startIndex = m.start();
            String tagName = m.group(1);

            Pattern endP = Pattern.compile("</" + tagName + "(\\s.*?)?>");
            Matcher endM = endP.matcher(theMessage);
            boolean foundEnd = endM.find(startIndex);

            if (foundEnd) {

                String fullMsg = theMessage.substring(startIndex, endM.end());
                Hl7V2MessageXml nextMsg = new Hl7V2MessageXml();
                nextMsg.setIndexWithinCollection(myMessages.size());
                nextMsg.setRuntimeProfile(myRuntimeProfile);
                nextMsg.setEncoding(Hl7V2EncodingTypeEnum.XML);
                try {
                    nextMsg.setSourceMessage(fullMsg);
                    myMessages.add(nextMsg);
                } catch (PropertyVetoException e) {
                    UnknownMessage nextUnk = new UnknownMessage(fullMsg);
                    myMessages.add(nextUnk);
                }

                rangeEnd = endM.end();

            } else {

                String fullMsg = theMessage.substring(startIndex);
                UnknownMessage nextUnk = new UnknownMessage(fullMsg);
                myMessages.add(nextUnk);
                rangeEnd = theMessage.length();

            }

            myMessageRanges.add(new Range(rangeStart, rangeEnd));
            rangeStart = rangeEnd;

        }

        // for (String nextString : msgs) {
        //
        // if (StringUtils.isNotBlank(nextString)) {
        //
        // nextString = "<?xml" + nextString;
        // Hl7V2MessageXml nextMsg = new Hl7V2MessageXml();
        // nextMsg.setIndexWithinCollection(myMessages.size());
        // nextMsg.setRuntimeProfile(myRuntimeProfile);
        // nextMsg.setEncoding(Hl7V2EncodingTypeEnum.XML);
        // try {
        // nextMsg.setSourceMessage(nextString);
        // myMessages.add(nextMsg);
        // } catch (PropertyVetoException e) {
        // UnknownMessage nextUnk = new UnknownMessage(nextString);
        // myMessages.add(nextUnk);
        // }
        //
        // }
        // }
    }

    private void ensureSourceMessage() {
        if (mySourceMessage == null) {
            mySourceMessage = "";
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object theObj) {
        if (!(theObj instanceof Hl7V2MessageCollection)) {
            return false;
        }
        Hl7V2MessageCollection o = (Hl7V2MessageCollection) theObj;
        return StringUtils.equals(myId, o.myId);
    }

    @Override
    public String exportConfigToXml() {
        XmlFormat xml = exportCreate();
        return exportWrite(xml);
    }

    public XmlFormat exportConfigToXmlWithoutContents() {
        XmlFormat xml = exportCreate();
        xml.mySourceMessage = null;
        return xml;
    }

    private XmlFormat exportCreate() {
        XmlFormat xml = new XmlFormat();

        xml.myId = this.myId;

        if (getSaveFileName() == null) {
            xml.mySourceMessage = this.mySourceMessage.replaceAll("((\r\n)|\r)|(\n)", "\n");
        }

        xml.mySaveCharsetName = mySaveCharset != null ? mySaveCharset.name() : "";
        xml.mySaved = this.mySaved;
        xml.mySaveFileName = StringUtils.isNotBlank(mySaveFileName) ? mySaveFileName : "";
        xml.myEncodingType = myEncoding.name();
        xml.myValidationContextClass = myValidationContext != null ? myValidationContext.getClass().getName() : "";
        xml.myProfileId = myProfileId != null ? myProfileId : "";
        xml.mySaveStripComments = Boolean.toString(myStripSaveComments);
        xml.mySaveLineEndings = mySaveLineEndings != null ? mySaveLineEndings.name() : "";
        xml.mySaveTimestamp = mySaveFileTimestamp;
        xml.myLastSendToInterfaceId = myLastSendToInterfaceId;
        xml.myEditorShowMode = getEditorShowMode();
        xml.mySendNumberOfTimes = mySendNumberOfTimes;
        return xml;
    }

    private String exportWrite(XmlFormat xml) {
        StringWriter stringWriter = new StringWriter();
        JAXB.marshal(xml, stringWriter);
        String string = stringWriter.toString();
        return string;
    }

    private synchronized int findSegmentMsgIndex(Message message) {
        int msgIndex = -1;
        for (int i = 0; i < myMessages.size(); i++) {
            Object nextParsed = myMessages.get(i).getParsedMessage();
            if (nextParsed == message) {
                msgIndex = i;
                break;
            }
        }
        return msgIndex;
    }

    private String fixLineEndings(String theSourceMessage, LineEndingsEnum theEndings) {
        StringBuilder b = new StringBuilder(theSourceMessage.length());

        for (int i = 0; i < theSourceMessage.length(); i++) {

            char next = theSourceMessage.charAt(i);
            boolean isEnd = false;
            if (next == '\r') {
                isEnd = true;
            } else if (next == '\n') {
                if (i > 0 && theSourceMessage.charAt(i - 1) == '\r') {
                    continue;
                } else {
                    isEnd = true;
                }
            }

            if (isEnd) {
                if (theEndings == LineEndingsEnum.UNIX) {
                    b.append('\n');
                } else if (theEndings == LineEndingsEnum.HL7) {
                    b.append('\r');
                } else if (theEndings == LineEndingsEnum.WINDOWS) {
                    b.append('\r');
                    b.append('\n');
                }
            } else {
                b.append(next);
            }

        }

        return b.toString();
    }

    /**
     * Returns whatever description of this message is most appropriate for
     * dialogs relating to it (such as a prompt to save before closing)
     */
    public String getBestDescription() {
        if (mySaveFileName != null) {
            return mySaveFileName;
        }
        return getMessageDescription();
    }

    /**
     * @return the show
     */
    public ShowEnum getEditorShowMode() {
        if (myShow == null) {
            myShow = ShowEnum.POPULATED;
        }
        return myShow;
    }

    public Hl7V2EncodingTypeEnum getEncoding() {
        return myEncoding;
    }

    public String getHighlitedPath() {
        return myHighlitedPath;
    }

    public String getId() {
        return myId;
    }

    /**
     * @return the lastSendToInterfaceId
     */
    public String getLastSendToInterfaceId() {
        return myLastSendToInterfaceId;
    }

    public String getMessageDescription() {
        if (myMessageDescription == null) {
            updateMessageDescription();
        }
        return myMessageDescription;
    }

    /**
     * @return the messageRanges
     */
    public List<Range> getMessageRanges() {
        return Collections.unmodifiableList(myMessageRanges);
    }

    /**
     * @return the messages
     */
    public List<AbstractMessage<?>> getMessages() {
        return myMessages;
    }

    /**
     * @return the runtimeProfile
     */
    public ProfileGroup getRuntimeProfile() {
        return myRuntimeProfile;
    }

    /**
     * @return the saveCharset
     */
    public Charset getSaveCharset() {
        return mySaveCharset;
    }

    /**
     * @return the fileName
     */
    public String getSaveFileName() {
        return mySaveFileName;
    }

    /**
     * @return the saveFileTimestamp
     */
    public long getSaveFileTimestamp() {
        return mySaveFileTimestamp;
    }

    /**
     * @return the saveLineEndings
     */
    public LineEndingsEnum getSaveLineEndings() {
        return mySaveLineEndings;
    }

    /**
     * @return the sendNumberOfTimes
     */
    public int getSendNumberOfTimes() {
        if (mySendNumberOfTimes <= 0) {
            return 1;
        }
        return mySendNumberOfTimes;
    }

    public String getSourceMessage() {
        return mySourceMessage;
    }

    /**
     * @return the validationContext
     */
    public ValidationContext getValidationContext() {
        return myValidationContext;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(myId).toHashCode();
    }

    /**
     * @return the saved
     */
    public boolean isSaved() {
        return mySaved;
    }

    /**
     * @return the saveComments
     */
    public boolean isSaveStripComments() {
        return myStripSaveComments;
    }

    public boolean isValidating() {
        return myValidationContext != null || myRuntimeProfile != null;
    }

    /**
     * @param theShow
     *            the show to set
     */
    public void setEditorShowMode(ShowEnum theShow) {
        myShow = theShow;
    }

    public void setEncoding(Hl7V2EncodingTypeEnum theEncoding) {
        Validate.notNull(theEncoding);

        Hl7V2EncodingTypeEnum oldEncodingValue = myEncoding;
        if (myEncoding != theEncoding) {
            myEncoding = theEncoding;

            switch (myEncoding) {
            case ER_7:
                myParser.setPipeParserAsPrimary();
                break;
            case XML:
                myParser.setXMLParserAsPrimary();
                break;
            }

            StringBuilder b = new StringBuilder();
            myMessageRanges.clear();

            for (int i = 0; i < myMessages.size(); i++) {
                int start = b.length();

                AbstractMessage<?> nextObj = myMessages.get(i);
                if (nextObj instanceof Hl7V2MessageBase) {

                    Hl7V2MessageBase messageImpl = (Hl7V2MessageBase) nextObj;
                    messageImpl = messageImpl.asEncoding(myEncoding);

                    b.append(messageImpl.getSourceMessage());
                    myMessages.set(i, messageImpl);

                } else if (nextObj instanceof UnknownMessage) {

                    UnknownMessage messageImpl = (UnknownMessage) nextObj;
                    b.append(messageImpl.getSourceMessage());

                }

                b.append("\r\r");
                int end = b.length();
                myMessageRanges.add(new Range(start, end));
            }

            String oldValue = mySourceMessage;
            mySourceMessage = b.toString();
            firePropertyChange(SOURCE_MESSAGE_PROPERTY, oldValue, mySourceMessage);

            if (StringUtils.equals(oldValue, mySourceMessage) == false) {
                setSaved(false);
            }

            firePropertyChange(PROP_ENCODING, oldEncodingValue, myEncoding);
        }
    }

    public void setHighlitedPathBasedOnRange(Range theRange) {

        final String oldValue = myHighlitedPath;

        if (theRange == null) {
            myHighlitedPath = null;
        } else {
            for (int i = 0; i < myMessageRanges.size(); i++) {
                Range range = myMessageRanges.get(i);
                if (range.contains(theRange.getStart())) {
                    AbstractMessage<?> am = myMessages.get(i);
                    if (am instanceof Hl7V2MessageBase) {
                        Hl7V2MessageBase messageImpl = (Hl7V2MessageBase) am;
                        messageImpl.setHighlitedPathBasedOnRange(theRange.add(-range.getStart()));
                        myHighlitedPath = i + messageImpl.getHighlitedPath();
                    }
                    break;
                }
            }
        } // if-else

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                firePropertyChange(PROP_HIGHLITED_PATH, oldValue, myHighlitedPath);
                updateMessageDescription();
            }
        });

    }

    public void setHighlitedRangeBasedOnField(SegmentAndComponentPath thePath) {
        Segment segment = thePath.getSegment();
        Message message = segment.getMessage();
        int msgIndex = findSegmentMsgIndex(message);

        Range oldValue = myHighlitedRange;
        if (msgIndex != -1) {
            Hl7V2MessageBase msg = (Hl7V2MessageBase) myMessages.get(msgIndex);
            msg.setHighlitedField(thePath);
            myHighlitedRange = msg.getHighlitedRange();
            if (myHighlitedRange != null) {
                myHighlitedRange = myHighlitedRange.add(myMessageRanges.get(msgIndex).getStart());
            } else {
                ourLog.info("No highlited range");
            }
        } else {
            ourLog.info("Couldn't find message");
        }

        firePropertyChange(PROP_HIGHLITED_RANGE, oldValue, myHighlitedRange);
    }

    public void setHighlitedRangeBasedOnSegment(Segment... theSegment) {
        Range oldValue = myHighlitedRange;

        if (theSegment == null || theSegment.length == 0) {
            myHighlitedRange = null;
            firePropertyChange(PROP_HIGHLITED_RANGE, oldValue, myHighlitedRange);
            return;
        }

        Message message = theSegment[0].getMessage();
        int msgIndex = findSegmentMsgIndex(message);

        if (msgIndex != -1) {
            Hl7V2MessageBase msg = (Hl7V2MessageBase) myMessages.get(msgIndex);
            msg.setHighlitedRangeBasedOnSegment(theSegment);
            myHighlitedRange = msg.getHighlitedRange();
            if (myHighlitedRange != null) {
                myHighlitedRange = myHighlitedRange.add(myMessageRanges.get(msgIndex).getStart());
            } else {
                ourLog.info("No highlited range");
            }
        } else {
            ourLog.info("Couldn't find message");
        }

        firePropertyChange(PROP_HIGHLITED_RANGE, oldValue, myHighlitedRange);

    }

    private void setId(String theId) {
        myId = theId;
    }

    /**
     * @param theLastSendToInterfaceId
     *            the lastSendToInterfaceId to set
     */
    public void setLastSendToInterfaceId(String theLastSendToInterfaceId) {
        myLastSendToInterfaceId = theLastSendToInterfaceId;
    }

    public void setRuntimeProfile(ProfileGroup theProfileGroup) throws ProfileException {
        if (theProfileGroup == null && myRuntimeProfile == null && myValidationContext == null) {
            return;
        }

        if (theProfileGroup == null && myRuntimeProfile == null && myValidationContext != null) {
            myValidationContext = null;
            myRuntimeProfile = null;
            myProfileId = null;
            firePropertyChange(PROP_VALIDATIONCONTEXT_OR_PROFILE, null, null);
            return;
        }

        myRuntimeProfile = theProfileGroup;
        myValidationContext = null;
        myProfileId = theProfileGroup.getId();

        for (AbstractMessage<?> next : myMessages) {
            if (next instanceof Hl7V2MessageBase) {
                ((Hl7V2MessageBase) next).setRuntimeProfile(myRuntimeProfile);
            }
        }

        firePropertyChange(PROP_VALIDATIONCONTEXT_OR_PROFILE, null, null);
    }

    /**
     * @param theSaveCharset
     *            the saveCharset to set
     */
    public void setSaveCharset(Charset theSaveCharset) {
        mySaveCharset = theSaveCharset;
    }

    /**
     * @param theSaved
     *            the saved to set
     */
    public void setSaved(boolean theSaved) {
        boolean oldValue = mySaved;
        mySaved = theSaved;
        firePropertyChange(SAVED_PROPERTY, oldValue, mySaved);
    }

    /**
     * @param theFileName
     *            the fileName to set
     */
    public void setSaveFileName(String theFileName) {
        String oldValue = null;
        mySaveFileName = theFileName;
        firePropertyChange(PROP_SAVE_FILENAME, oldValue, theFileName);

        updateMessageDescription();
    }

    /**
     * @param theSaveFileTimestamp
     *            the saveFileTimestamp to set
     */
    public void setSaveFileTimestamp(long theSaveFileTimestamp) {
        mySaveFileTimestamp = theSaveFileTimestamp;
    }

    public void setSaveLineEndings(LineEndingsEnum theLineEndings) {
        mySaveLineEndings = theLineEndings;
    }

    public void setSaveStripComments(boolean theSaveComments) {
        myStripSaveComments = theSaveComments;
    }

    /**
     * @param theSendNumberOfTimes
     *            the sendNumberOfTimes to set
     */
    public void setSendNumberOfTimes(int theSendNumberOfTimes) {
        mySendNumberOfTimes = theSendNumberOfTimes;
    }

    public void setSourceMessage(String theSourceMessage) {
        ourLog.info("About to set source message for collection");

        ArrayList<AbstractMessage<?>> oldMessages = new ArrayList<AbstractMessage<?>>(myMessages);
        myMessages.clear();
        myMessageRanges.clear();

        String message = fixLineEndings(theSourceMessage, LineEndingsEnum.HL7);

        if (myEncoding == Hl7V2EncodingTypeEnum.ER_7) {
            doSetSourceMessageEr7(message);
        } else {
            doSetSourceMessageXml(message);
        }

        String oldValue = mySourceMessage;
        mySourceMessage = theSourceMessage;

        ourLog.info("Firing message change event");

        firePropertyChange(PARSED_MESSAGES_PROPERTY, oldMessages, myMessages);
        firePropertyChange(SOURCE_MESSAGE_PROPERTY, oldValue, mySourceMessage);

        if (StringUtils.equals(oldValue, mySourceMessage) == false) {
            setSaved(false);
        }

        updateMessageDescription();

        ourLog.info("Done setting source message for collection");
    }

    /**
     * @param theValidationContext
     *            the validationContext to set
     */
    public void setValidationContext(ValidationContext theValidationContext) {
        if (myValidationContext instanceof DefaultValidation && theValidationContext instanceof DefaultValidation) {
            return;
        }

        if (theValidationContext != null) {
            myRuntimeProfile = null;
            myRuntimeProfile = null;
        }

        ourLog.info("Setting validation context to: " + theValidationContext);

        for (AbstractMessage<?> next : myMessages) {
            if (next instanceof Hl7V2MessageBase) {
                ((Hl7V2MessageBase) next).setRuntimeProfile(null);
            }
        }

        ValidationContext oldValue = myValidationContext;
        myValidationContext = theValidationContext;
        firePropertyChange(PROP_VALIDATIONCONTEXT_OR_PROFILE, oldValue, theValidationContext);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return Hl7V2MessageCollection.class.getSimpleName() + "[" + getSaveFileName() + "]";
    }

    private void updateMessageDescription() {
        String oldValue = myMessageDescription;

        Matcher matcher = FIRSTLINE_COMMENT_PATTERN.matcher(StringUtils.defaultString(mySourceMessage));
        if (matcher.find()) {

            myMessageDescription = matcher.group(1).trim();

        } else if (mySaveFileName == null) {

            int msgs = 0;
            AbstractMessage<?> firstNonComment = null;
            for (AbstractMessage<?> next : myMessages) {
                if (!(next instanceof Comment)) {
                    if (msgs == 0) {
                        firstNonComment = next;
                    }
                    msgs++;
                }
            }

            if (msgs == 0) {
                myMessageDescription = "None";
            } else if (msgs == 1) {
                if (firstNonComment instanceof UnknownMessage) {
                    myMessageDescription = "Unknown";
                } else if (firstNonComment instanceof Hl7V2MessageBase) {
                    myMessageDescription = ((Hl7V2MessageBase) firstNonComment).getMessageDescription();
                } else {
                    myMessageDescription = "None";
                }
            } else {
                myMessageDescription = msgs + " messages";
            }

        } else {

            myMessageDescription = mySaveFileName.replaceAll(".*(\\\\|\\/)", "");

        }

        firePropertyChange(PROP_DESCRIPTION, oldValue, myMessageDescription);
    }

    public void updateSourceMessage(String theNewSource, int theChangeStart, int theChangeEnd) {
        // TODO: optimize this to only update changed bits
        setSourceMessage(theNewSource);
    }

    private synchronized void updateSourceMessageBasedOnEncodedMessage(int theMessageIndex,
            String theEncodedMessage) {
        setHighlitedPathBasedOnRange(null);

        Range range = myMessageRanges.get(theMessageIndex);

        String oldValue = mySourceMessage;

        String preMessage = mySourceMessage.substring(0, range.getStart());
        String postMessage = mySourceMessage.length() > range.getEnd() ? mySourceMessage.substring(range.getEnd())
                : "";
        mySourceMessage = preMessage + theEncodedMessage + postMessage;

        int sizeDifference = theEncodedMessage.length() - range.getDelta();
        for (int i = theMessageIndex; i < myMessageRanges.size(); i++) {
            if (i == theMessageIndex) {
                myMessageRanges.set(i, myMessageRanges.get(i).addToEnd(sizeDifference));
            } else {
                myMessageRanges.set(i, myMessageRanges.get(i).addToBoth(sizeDifference));
            }
        }

        Hl7V2MessageBase msgBase = (Hl7V2MessageBase) myMessages.get(theMessageIndex);
        try {
            msgBase.setSourceMessage(theEncodedMessage);
        } catch (PropertyVetoException e) {
            ourLog.error("Failed to update message", e);
        }

        firePropertyChange(SOURCE_MESSAGE_PROPERTY, oldValue, mySourceMessage);

        if (StringUtils.equals(oldValue, mySourceMessage) == false) {
            setSaved(false);
        }

    }

    public void updateSourceMessageBasedOnParsedMessage(int theMessageIndex, Message theMsg) {
        String encodedMessage;
        try {
            encodedMessage = myParser.encode(theMsg);
        } catch (HL7Exception e) {
            ourLog.error("Failed to encode message", e);
            return;
        }

        updateSourceMessageBasedOnEncodedMessage(theMessageIndex, encodedMessage);
    }

    /**
     * Write the messages to an output stream
     */
    public void writeToFile(Writer theWriter, boolean theSelectedSaveStripComments,
            LineEndingsEnum theSelectedLineEndings) throws IOException {
        String toWrite = mySourceMessage;

        if (theSelectedSaveStripComments) {
            switch (myEncoding) {
            case ER_7:

                Pattern p = Pattern.compile("(^|\\r)\\s*#[^\\r]*");
                Matcher m = p.matcher(toWrite);
                toWrite = m.replaceAll("").trim() + "\r";

                p = Pattern.compile("\\r\\s*\\r");
                m = p.matcher(toWrite);
                toWrite = m.replaceAll("\r");

                break;
            }
        }

        toWrite = fixLineEndings(toWrite, theSelectedLineEndings);

        theWriter.append(toWrite);
    }

    public static Hl7V2MessageCollection fromXml(ProfileFileList theProfileFileList, String theContents) {
        XmlFormat xmlFormat = JAXB.unmarshal(new StringReader(theContents), XmlFormat.class);
        return fromXml(theProfileFileList, xmlFormat);
    }

    public static Hl7V2MessageCollection fromXml(ProfileFileList theProfileFileList, XmlFormat theXmlFormat) {

        Hl7V2MessageCollection retVal = new Hl7V2MessageCollection();

        retVal.setSaveFileName(isNotBlank(theXmlFormat.mySaveFileName) ? theXmlFormat.mySaveFileName : null);
        retVal.setId(theXmlFormat.myId);
        retVal.setLastSendToInterfaceId(theXmlFormat.myLastSendToInterfaceId);
        retVal.setEditorShowMode(theXmlFormat.myEditorShowMode);
        retVal.setSendNumberOfTimes(theXmlFormat.mySendNumberOfTimes);

        try {
            retVal.setSaveCharset(
                    isNotEmpty(theXmlFormat.mySaveCharsetName) ? Charset.forName(theXmlFormat.mySaveCharsetName)
                            : null);
        } catch (IllegalCharsetNameException e) {
            // ignore
        }

        if (isNotBlank(theXmlFormat.myValidationContextClass)) {
            retVal.setValidationContext(ClassUtils.instantiateOrReturnNull(theXmlFormat.myValidationContextClass,
                    ValidationContext.class));
        }

        if (isNotBlank(theXmlFormat.myProfileId)) {
            try {
                retVal.setRuntimeProfile(theProfileFileList.getProfile(theXmlFormat.myProfileId));
            } catch (ProfileException e) {
                ourLog.error("Failed to retrieve profile with id: " + theXmlFormat.myProfileId, e);
            }
        }

        try {
            retVal.setSaveLineEndings(LineEndingsEnum.valueOf(theXmlFormat.mySaveLineEndings));
        } catch (Exception e) {
            retVal.setSaveLineEndings(LineEndingsEnum.HL7);
        }

        retVal.setSaveStripComments(Boolean.parseBoolean(theXmlFormat.mySaveStripComments));

        retVal.setEncoding(
                "XML".equals(theXmlFormat.myEncodingType) ? Hl7V2EncodingTypeEnum.XML : Hl7V2EncodingTypeEnum.ER_7);

        if (StringUtils.isNotBlank(theXmlFormat.mySourceMessage)) {
            retVal.setSourceMessage(theXmlFormat.mySourceMessage);
        } else {
            retVal.setSourceMessage("");
        }

        retVal.setSaveFileTimestamp(theXmlFormat.mySaveTimestamp);

        // set this last!
        retVal.setSaved(theXmlFormat.mySaved);

        return retVal;
    }

    public static void main(String[] args) {

        System.out.println(Pattern.compile("<([A-Z0-9_]+)").matcher("<ORU_R01 xmlns=\"ffdf\">aaaaa").find());

    }

    private enum ParserState {
        AT_MSG_START, COMMENT, IN_MSG, NONE, UNKNOWN
    }

    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlRootElement(name = "Hl7V2MessageCollection", namespace = "urn:ca.uhn.hapi:testpanel")
    public static class XmlFormat {

        @XmlAttribute(required = false, name = "editorShowMode")
        public ShowEnum myEditorShowMode;

        @XmlAttribute(required = true, name = "encodingType")
        public String myEncodingType;

        @XmlAttribute(required = true, name = "id")
        public String myId;

        @XmlAttribute(required = false, name = "lastSendToInterfaceId")
        private String myLastSendToInterfaceId;

        @XmlElement(required = true, name = "profileId")
        public String myProfileId;

        @XmlAttribute(required = true, name = "charsetName")
        private String mySaveCharsetName;

        @XmlAttribute(required = true, name = "saved")
        private boolean mySaved;

        @XmlAttribute(required = true, name = "saveFileName")
        private String mySaveFileName;

        @XmlAttribute(required = true, name = "saveLineEndings")
        private String mySaveLineEndings;

        @XmlAttribute(required = true, name = "saveStripComments")
        private String mySaveStripComments;

        @XmlAttribute(required = true, name = "saveFileTimestamp")
        private long mySaveTimestamp;

        @XmlAttribute(required = false, name = "sendNumberOfTimes")
        public int mySendNumberOfTimes;

        @XmlElement(required = true, name = "sourceMessage")
        public String mySourceMessage;

        @XmlElement(required = true, name = "validationContextClass")
        public String myValidationContextClass;

    }

}