View Javadoc
1   /**
2    * The contents of this file are subject to the Mozilla Public License Version 1.1
3    * (the "License"); you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at http://www.mozilla.org/MPL/
5    * Software distributed under the License is distributed on an "AS IS" basis,
6    * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
7    * specific language governing rights and limitations under the License.
8    *
9    * The Original Code is ""  Description:
10   * ""
11   *
12   * The Initial Developer of the Original Code is University Health Network. Copyright (C)
13   * 2001.  All Rights Reserved.
14   *
15   * Contributor(s): ______________________________________.
16   *
17   * Alternatively, the contents of this file may be used under the terms of the
18   * GNU General Public License (the  "GPL"), in which case the provisions of the GPL are
19   * applicable instead of those above.  If you wish to allow use of your version of this
20   * file only under the terms of the GPL and not to allow others to use your version
21   * of this file under the MPL, indicate your decision by deleting  the provisions above
22   * and replace  them with the notice and other provisions required by the GPL License.
23   * If you do not delete the provisions above, a recipient may use your version of
24   * this file under either the MPL or the GPL.
25   */
26  package ca.uhn.hl7v2.testpanel.model.msg;
27  
28  import static org.apache.commons.lang.StringUtils.*;
29  
30  import java.awt.EventQueue;
31  import java.beans.PropertyVetoException;
32  import java.io.IOException;
33  import java.io.StringReader;
34  import java.io.StringWriter;
35  import java.io.Writer;
36  import java.nio.charset.Charset;
37  import java.nio.charset.IllegalCharsetNameException;
38  import java.util.ArrayList;
39  import java.util.Collections;
40  import java.util.List;
41  import java.util.UUID;
42  import java.util.regex.Matcher;
43  import java.util.regex.Pattern;
44  
45  import jakarta.xml.bind.JAXB;
46  import jakarta.xml.bind.annotation.XmlAccessType;
47  import jakarta.xml.bind.annotation.XmlAccessorType;
48  import jakarta.xml.bind.annotation.XmlAttribute;
49  import jakarta.xml.bind.annotation.XmlElement;
50  import jakarta.xml.bind.annotation.XmlRootElement;
51  
52  import org.apache.commons.lang.StringUtils;
53  import org.apache.commons.lang.Validate;
54  import org.apache.commons.lang.builder.HashCodeBuilder;
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  import ca.uhn.hl7v2.HL7Exception;
59  import ca.uhn.hl7v2.conf.ProfileException;
60  import ca.uhn.hl7v2.model.Message;
61  import ca.uhn.hl7v2.model.Segment;
62  import ca.uhn.hl7v2.parser.GenericParser;
63  import ca.uhn.hl7v2.testpanel.model.AbstractModelClass;
64  import ca.uhn.hl7v2.testpanel.model.UnknownMessage;
65  import ca.uhn.hl7v2.testpanel.model.conf.ProfileFileList;
66  import ca.uhn.hl7v2.testpanel.model.conf.ProfileGroup;
67  import ca.uhn.hl7v2.testpanel.ui.ShowEnum;
68  import ca.uhn.hl7v2.testpanel.util.ClassUtils;
69  import ca.uhn.hl7v2.testpanel.util.LineEndingsEnum;
70  import ca.uhn.hl7v2.testpanel.util.Range;
71  import ca.uhn.hl7v2.testpanel.util.SegmentAndComponentPath;
72  import ca.uhn.hl7v2.testpanel.xsd.Hl7V2EncodingTypeEnum;
73  import ca.uhn.hl7v2.validation.ValidationContext;
74  import ca.uhn.hl7v2.validation.impl.DefaultValidation;
75  import ca.uhn.hl7v2.validation.impl.ValidationContextImpl;
76  
77  public class Hl7V2MessageCollection extends AbstractModelClass {
78  	private static final Pattern FIRSTLINE_COMMENT_PATTERN = Pattern.compile("^\\#\\s*(\\S.*)");
79  	private static final Logger ourLog = LoggerFactory.getLogger(Hl7V2MessageCollection.class);
80  	public static final String PARSED_MESSAGES_PROPERTY = Hl7V2MessageCollection.class.getName() + "PARSED_MESSAGES_PROPERTY";
81  	public static final String PROP_DESCRIPTION = Hl7V2MessageCollection.class.getName() + "DESCRIPTION";
82  	public static final String PROP_ENCODING = Hl7V2MessageCollection.class.getName() + "ENCODING";
83  	public static final String PROP_HIGHLITED_PATH = Hl7V2MessageCollection.class.getName() + "HIGHLITED_PATH";
84  	public static final String PROP_HIGHLITED_RANGE = Hl7V2MessageCollection.class.getName() + "HIGHLITED_RANGE";
85  	public static final String PROP_SAVE_FILENAME = Hl7V2MessageCollection.class.getName() + "SAVE_FILENAME";
86  	public static final String PROP_VALIDATIONCONTEXT_OR_PROFILE = Hl7V2MessageCollection.class.getName() + "VALIDATIONCONTEXT";
87  	public static final String SAVED_PROPERTY = AbstractMessage.class.getName() + "SAVED_PROPERTY";
88  	public static final String SOURCE_MESSAGE_PROPERTY = Hl7V2MessageCollection.class.getName() + "SOURCE_MESSAGE_PROPERTY";
89  
90  	private Hl7V2EncodingTypeEnum myEncoding = Hl7V2EncodingTypeEnum.ER_7;
91  	private String myHighlitedPath;
92  	private Range myHighlitedRange;
93  	private String myId;
94  	private String myLastSendToInterfaceId;
95  	private String myMessageDescription;
96  	private List<Range> myMessageRanges = new ArrayList<Range>();
97  	private List<AbstractMessage<?>> myMessages = new ArrayList<AbstractMessage<?>>();
98  	private GenericParser myParser;
99  	private String myProfileId;
100 	private ProfileGroup myRuntimeProfile;
101 	private Charset mySaveCharset;
102 	private boolean mySaved;
103 	private String mySaveFileName;
104 	private long mySaveFileTimestamp;
105 	private LineEndingsEnum mySaveLineEndings;
106 	private int mySendNumberOfTimes = 1;
107 	private ShowEnum myShow;
108 	private String mySourceMessage;
109 	private boolean myStripSaveComments;
110 	private ValidationContext myValidationContext = new DefaultValidation();
111 
112 	/**
113 	 * Constructor
114 	 */
115 	public Hl7V2MessageCollection() {
116 		myParser = new GenericParser();
117 		myParser.setValidationContext(new ValidationContextImpl());
118 
119 		myId = UUID.randomUUID().toString();
120 	}
121 
122 	public synchronized void addComment(String theComment) {
123 		String oldValue = mySourceMessage;
124 
125 		ensureSourceMessage();
126 
127 		StringBuilder b = new StringBuilder();
128 		if (myEncoding == Hl7V2EncodingTypeEnum.ER_7) {
129 			for (String next : theComment.split("\\n")) {
130 				if (StringUtils.isNotBlank(next)) {
131 					b.append("#");
132 					b.append(next);
133 					b.append("\n");
134 				} else {
135 					b.append("\n");
136 				}
137 			}
138 
139 		} else {
140 			b.append("<!--");
141 			b.append(theComment);
142 			b.append("-->\n");
143 		}
144 
145 		int originalLength = mySourceMessage.length();
146 		mySourceMessage += b.toString();
147 
148 		Comment comment = new Comment();
149 		comment.setSourceMessage(theComment);
150 		myMessages.add(comment);
151 
152 		ensureSourceMessage();
153 
154 		myMessageRanges.add(new Range(originalLength, originalLength + b.length()));
155 
156 		firePropertyChange(SOURCE_MESSAGE_PROPERTY, oldValue, mySourceMessage);
157 		firePropertyChange(PARSED_MESSAGES_PROPERTY, oldValue, mySourceMessage);
158 	}
159 
160 	public synchronized void addMessage(AbstractMessage<?> theMessage) {
161 		Validate.notNull(theMessage);
162 
163 		int newIndex = myMessages.size();
164 		myMessages.add(theMessage);
165 
166 		ensureSourceMessage();
167 
168 		myMessageRanges.add(new Range(mySourceMessage.length()));
169 
170 		if (theMessage instanceof Hl7V2MessageBase) {
171 			updateSourceMessageBasedOnParsedMessage(newIndex, (Message) theMessage.getParsedMessage());
172 		}
173 	}
174 
175 	public void clearHighlight() {
176 		Range oldValue = myHighlitedRange;
177 		myHighlitedRange = null;
178 		firePropertyChange(PROP_HIGHLITED_RANGE, oldValue, myHighlitedRange);
179 
180 	}
181 
182 	public int countMessagesOfType(Class<Hl7V2MessageBase> theClass) {
183 		int retVal = 0;
184 		for (AbstractMessage<?> next : getMessages()) {
185 			if (theClass.isAssignableFrom(next.getClass())) {
186 				retVal++;
187 			}
188 		}
189 		return retVal;
190 	}
191 
192 	private synchronized void doSetSourceMessageEr7(String message) {
193 		String[] lines = message.split("\\r");
194 
195 		StringBuilder buf = new StringBuilder();
196 		ParserState curState = ParserState.NONE;
197 		int curStart = 0;
198 		for (String nextLine : lines) {
199 			ParserState newState;
200 			if (nextLine.startsWith("#")) {
201 				newState = ParserState.COMMENT;
202 			} else if (nextLine.startsWith("MSH")) {
203 				newState = ParserState.AT_MSG_START;
204 			} else {
205 				if (nextLine.trim().length() > 0) {
206 					if (curState == ParserState.IN_MSG && nextLine.matches("^[A-Z][A-Z0-9][A-Z0-9].*")) {
207 						newState = ParserState.IN_MSG;
208 					} else {
209 						newState = ParserState.UNKNOWN;
210 					}
211 				} else {
212 					newState = ParserState.NONE;
213 				}
214 			}
215 
216 			if (curState != newState) {
217 				AbstractMessage<?> msg = null;
218 				String bufToString = buf.toString();
219 				if (bufToString.length() > 0) {
220 					switch (curState) {
221 					case AT_MSG_START:
222 					case IN_MSG:
223 						try {
224 
225 							// Note: this code is duplicated below.. that should
226 							// be cleaned
227 							// up probably, but until then if this is modified,
228 							// modify it below
229 							// also
230 							ourLog.info("Found ER7 message");
231 							Hl7V2MessageBase hl7msg = new Hl7V2MessageEr7();
232 							hl7msg.setIndexWithinCollection(myMessages.size());
233 							hl7msg.setRuntimeProfile(myRuntimeProfile);
234 							hl7msg.setSourceMessage(bufToString);
235 							msg = hl7msg;
236 
237 						} catch (PropertyVetoException e) {
238 							msg = new UnknownMessage(bufToString);
239 						}
240 						break;
241 					case COMMENT:
242 						msg = new Comment(bufToString);
243 						break;
244 					case UNKNOWN:
245 						msg = new UnknownMessage(bufToString);
246 						break;
247 					}
248 
249 					if (msg != null) {
250 						myMessages.add(msg);
251 						myMessageRanges.add(new Range(curStart, curStart + bufToString.length()));
252 					}
253 				}
254 
255 				curStart = curStart + bufToString.length();
256 				buf.setLength(0);
257 
258 				if (newState == ParserState.AT_MSG_START) {
259 					curState = ParserState.IN_MSG;
260 				} else {
261 					curState = newState;
262 				}
263 			}
264 
265 			buf.append(nextLine).append('\r');
266 
267 		}
268 
269 		String bufToString = buf.toString();
270 		if (bufToString.trim().length() > 0) {
271 			AbstractMessage<?> msg = null;
272 			switch (curState) {
273 			case IN_MSG:
274 			case AT_MSG_START:
275 				try {
276 
277 					ourLog.info("Found ER7 message");
278 					Hl7V2MessageBase hl7msg = new Hl7V2MessageEr7();
279 					hl7msg.setIndexWithinCollection(myMessages.size());
280 					hl7msg.setRuntimeProfile(myRuntimeProfile);
281 					hl7msg.setSourceMessage(bufToString);
282 					msg = hl7msg;
283 
284 				} catch (PropertyVetoException e) {
285 					msg = new UnknownMessage(bufToString);
286 				}
287 				break;
288 			case COMMENT:
289 				msg = new Comment(bufToString);
290 				break;
291 			case UNKNOWN:
292 				msg = new UnknownMessage(bufToString);
293 				break;
294 			}
295 
296 			if (msg != null) {
297 				myMessages.add(msg);
298 				myMessageRanges.add(new Range(curStart, curStart + bufToString.length()));
299 			}
300 
301 		}
302 
303 		// List<String> b = new ArrayList<String>();
304 		// StringBuilder fullMsgBuilder = new StringBuilder();
305 		// int start = 0;
306 		// int end = 0;
307 		// int charIndex = 0;
308 		// boolean inMsg = false;
309 		// for (String line : lines) {
310 		//
311 		// if (line.startsWith("MSH")) {
312 		// addMessageIfAny(b, start, end);
313 		// b.clear();
314 		// start = charIndex;
315 		// inMsg = true;
316 		// }
317 		//
318 		// if (line.trim().length() == 0) {
319 		// if (inMsg) {
320 		// addMessageIfAny(b, start, end);
321 		// b.clear();
322 		// inMsg = false;
323 		// }
324 		// } else if (!line.matches("^[A-Z][A-Z0-9]{2}.*") && inMsg) {
325 		// addMessageIfAny(b, start, end);
326 		// b.clear();
327 		// inMsg = false;
328 		// }
329 		//
330 		// b.add(line);
331 		//
332 		// fullMsgBuilder.append(line);
333 		// fullMsgBuilder.append('\r');
334 		//
335 		// charIndex += line.length();
336 		// charIndex++;
337 		// end = charIndex;
338 		// }
339 		//
340 		// addMessageIfAny(b, start, end);
341 	}
342 
343 	private synchronized void doSetSourceMessageXml(String theMessage) {
344 		int rangeStart = 0;
345 		int rangeEnd = 0;
346 
347 		Pattern p = Pattern.compile("<([A-Za-z0-9_]+).*?>");
348 		Matcher m = p.matcher(theMessage);
349 
350 		while (rangeStart < theMessage.length() && m.find(rangeStart)) {
351 
352 			int startIndex = m.start();
353 			String tagName = m.group(1);
354 
355 			Pattern endP = Pattern.compile("</" + tagName + "(\\s.*?)?>");
356 			Matcher endM = endP.matcher(theMessage);
357 			boolean foundEnd = endM.find(startIndex);
358 
359 			if (foundEnd) {
360 
361 				String fullMsg = theMessage.substring(startIndex, endM.end());
362 				Hl7V2MessageXml nextMsg = new Hl7V2MessageXml();
363 				nextMsg.setIndexWithinCollection(myMessages.size());
364 				nextMsg.setRuntimeProfile(myRuntimeProfile);
365 				nextMsg.setEncoding(Hl7V2EncodingTypeEnum.XML);
366 				try {
367 					nextMsg.setSourceMessage(fullMsg);
368 					myMessages.add(nextMsg);
369 				} catch (PropertyVetoException e) {
370 					UnknownMessage nextUnk = new UnknownMessage(fullMsg);
371 					myMessages.add(nextUnk);
372 				}
373 
374 				rangeEnd = endM.end();
375 
376 			} else {
377 
378 				String fullMsg = theMessage.substring(startIndex);
379 				UnknownMessage nextUnk = new UnknownMessage(fullMsg);
380 				myMessages.add(nextUnk);
381 				rangeEnd = theMessage.length();
382 
383 			}
384 
385 			myMessageRanges.add(new Range(rangeStart, rangeEnd));
386 			rangeStart = rangeEnd;
387 
388 		}
389 
390 		// for (String nextString : msgs) {
391 		//
392 		// if (StringUtils.isNotBlank(nextString)) {
393 		//
394 		// nextString = "<?xml" + nextString;
395 		// Hl7V2MessageXml nextMsg = new Hl7V2MessageXml();
396 		// nextMsg.setIndexWithinCollection(myMessages.size());
397 		// nextMsg.setRuntimeProfile(myRuntimeProfile);
398 		// nextMsg.setEncoding(Hl7V2EncodingTypeEnum.XML);
399 		// try {
400 		// nextMsg.setSourceMessage(nextString);
401 		// myMessages.add(nextMsg);
402 		// } catch (PropertyVetoException e) {
403 		// UnknownMessage nextUnk = new UnknownMessage(nextString);
404 		// myMessages.add(nextUnk);
405 		// }
406 		//
407 		// }
408 		// }
409 	}
410 
411 	private void ensureSourceMessage() {
412 		if (mySourceMessage == null) {
413 			mySourceMessage = "";
414 		}
415 	}
416 
417 	/**
418 	 * {@inheritDoc}
419 	 */
420 	@Override
421 	public boolean equals(Object theObj) {
422 		if (!(theObj instanceof Hl7V2MessageCollection)) {
423 			return false;
424 		}
425 		Hl7V2MessageCollection o = (Hl7V2MessageCollection) theObj;
426 		return StringUtils.equals(myId, o.myId);
427 	}
428 
429 	@Override
430 	public String exportConfigToXml() {
431 		XmlFormat xml = exportCreate();
432 		return exportWrite(xml);
433 	}
434 
435 	public XmlFormat exportConfigToXmlWithoutContents() {
436 		XmlFormat xml = exportCreate();
437 		xml.mySourceMessage = null;
438 		return xml;
439 	}
440 
441 	private XmlFormat exportCreate() {
442 		XmlFormat xml = new XmlFormat();
443 
444 		xml.myId = this.myId;
445 
446 		if (getSaveFileName() == null) {
447 			xml.mySourceMessage = this.mySourceMessage.replaceAll("((\r\n)|\r)|(\n)", "\n");
448 		}
449 
450 		xml.mySaveCharsetName = mySaveCharset != null ? mySaveCharset.name() : "";
451 		xml.mySaved = this.mySaved;
452 		xml.mySaveFileName = StringUtils.isNotBlank(mySaveFileName) ? mySaveFileName : "";
453 		xml.myEncodingType = myEncoding.name();
454 		xml.myValidationContextClass = myValidationContext != null ? myValidationContext.getClass().getName() : "";
455 		xml.myProfileId = myProfileId != null ? myProfileId : "";
456 		xml.mySaveStripComments = Boolean.toString(myStripSaveComments);
457 		xml.mySaveLineEndings = mySaveLineEndings != null ? mySaveLineEndings.name() : "";
458 		xml.mySaveTimestamp = mySaveFileTimestamp;
459 		xml.myLastSendToInterfaceId = myLastSendToInterfaceId;
460 		xml.myEditorShowMode = getEditorShowMode();
461 		xml.mySendNumberOfTimes = mySendNumberOfTimes;
462 		return xml;
463 	}
464 
465 	private String exportWrite(XmlFormat xml) {
466 		StringWriter stringWriter = new StringWriter();
467 		JAXB.marshal(xml, stringWriter);
468 		String string = stringWriter.toString();
469 		return string;
470 	}
471 
472 	private synchronized int findSegmentMsgIndex(Message message) {
473 		int msgIndex = -1;
474 		for (int i = 0; i < myMessages.size(); i++) {
475 			Object nextParsed = myMessages.get(i).getParsedMessage();
476 			if (nextParsed == message) {
477 				msgIndex = i;
478 				break;
479 			}
480 		}
481 		return msgIndex;
482 	}
483 
484 	private String fixLineEndings(String theSourceMessage, LineEndingsEnum theEndings) {
485 		StringBuilder b = new StringBuilder(theSourceMessage.length());
486 
487 		for (int i = 0; i < theSourceMessage.length(); i++) {
488 
489 			char next = theSourceMessage.charAt(i);
490 			boolean isEnd = false;
491 			if (next == '\r') {
492 				isEnd = true;
493 			} else if (next == '\n') {
494 				if (i > 0 && theSourceMessage.charAt(i - 1) == '\r') {
495 					continue;
496 				} else {
497 					isEnd = true;
498 				}
499 			}
500 
501 			if (isEnd) {
502 				if (theEndings == LineEndingsEnum.UNIX) {
503 					b.append('\n');
504 				} else if (theEndings == LineEndingsEnum.HL7) {
505 					b.append('\r');
506 				} else if (theEndings == LineEndingsEnum.WINDOWS) {
507 					b.append('\r');
508 					b.append('\n');
509 				}
510 			} else {
511 				b.append(next);
512 			}
513 
514 		}
515 
516 		return b.toString();
517 	}
518 
519 	/**
520 	 * Returns whatever description of this message is most appropriate for
521 	 * dialogs relating to it (such as a prompt to save before closing)
522 	 */
523 	public String getBestDescription() {
524 		if (mySaveFileName != null) {
525 			return mySaveFileName;
526 		}
527 		return getMessageDescription();
528 	}
529 
530 	/**
531 	 * @return the show
532 	 */
533 	public ShowEnum getEditorShowMode() {
534 		if (myShow == null) {
535 			myShow = ShowEnum.POPULATED;
536 		}
537 		return myShow;
538 	}
539 
540 	public Hl7V2EncodingTypeEnum getEncoding() {
541 		return myEncoding;
542 	}
543 
544 	public String getHighlitedPath() {
545 		return myHighlitedPath;
546 	}
547 
548 	public String getId() {
549 		return myId;
550 	}
551 
552 	/**
553 	 * @return the lastSendToInterfaceId
554 	 */
555 	public String getLastSendToInterfaceId() {
556 		return myLastSendToInterfaceId;
557 	}
558 
559 	public String getMessageDescription() {
560 		if (myMessageDescription == null) {
561 			updateMessageDescription();
562 		}
563 		return myMessageDescription;
564 	}
565 
566 	/**
567 	 * @return the messageRanges
568 	 */
569 	public List<Range> getMessageRanges() {
570 		return Collections.unmodifiableList(myMessageRanges);
571 	}
572 
573 	/**
574 	 * @return the messages
575 	 */
576 	public List<AbstractMessage<?>> getMessages() {
577 		return myMessages;
578 	}
579 
580 	/**
581 	 * @return the runtimeProfile
582 	 */
583 	public ProfileGroup getRuntimeProfile() {
584 		return myRuntimeProfile;
585 	}
586 
587 	/**
588 	 * @return the saveCharset
589 	 */
590 	public Charset getSaveCharset() {
591 		return mySaveCharset;
592 	}
593 
594 	/**
595 	 * @return the fileName
596 	 */
597 	public String getSaveFileName() {
598 		return mySaveFileName;
599 	}
600 
601 	/**
602 	 * @return the saveFileTimestamp
603 	 */
604 	public long getSaveFileTimestamp() {
605 		return mySaveFileTimestamp;
606 	}
607 
608 	/**
609 	 * @return the saveLineEndings
610 	 */
611 	public LineEndingsEnum getSaveLineEndings() {
612 		return mySaveLineEndings;
613 	}
614 
615 	/**
616 	 * @return the sendNumberOfTimes
617 	 */
618 	public int getSendNumberOfTimes() {
619 		if (mySendNumberOfTimes <= 0) {
620 			return 1;
621 		}
622 		return mySendNumberOfTimes;
623 	}
624 
625 	public String getSourceMessage() {
626 		return mySourceMessage;
627 	}
628 
629 	/**
630 	 * @return the validationContext
631 	 */
632 	public ValidationContext getValidationContext() {
633 		return myValidationContext;
634 	}
635 
636 	/**
637 	 * {@inheritDoc}
638 	 */
639 	@Override
640 	public int hashCode() {
641 		return new HashCodeBuilder().append(myId).toHashCode();
642 	}
643 
644 	/**
645 	 * @return the saved
646 	 */
647 	public boolean isSaved() {
648 		return mySaved;
649 	}
650 
651 	/**
652 	 * @return the saveComments
653 	 */
654 	public boolean isSaveStripComments() {
655 		return myStripSaveComments;
656 	}
657 
658 	public boolean isValidating() {
659 		return myValidationContext != null || myRuntimeProfile != null;
660 	}
661 
662 	/**
663 	 * @param theShow
664 	 *            the show to set
665 	 */
666 	public void setEditorShowMode(ShowEnum theShow) {
667 		myShow = theShow;
668 	}
669 
670 	public void setEncoding(Hl7V2EncodingTypeEnum theEncoding) {
671 		Validate.notNull(theEncoding);
672 
673 		Hl7V2EncodingTypeEnum oldEncodingValue = myEncoding;
674 		if (myEncoding != theEncoding) {
675 			myEncoding = theEncoding;
676 
677 			switch (myEncoding) {
678 			case ER_7:
679 				myParser.setPipeParserAsPrimary();
680 				break;
681 			case XML:
682 				myParser.setXMLParserAsPrimary();
683 				break;
684 			}
685 
686 			StringBuilder b = new StringBuilder();
687 			myMessageRanges.clear();
688 
689 			for (int i = 0; i < myMessages.size(); i++) {
690 				int start = b.length();
691 
692 				AbstractMessage<?> nextObj = myMessages.get(i);
693 				if (nextObj instanceof Hl7V2MessageBase) {
694 
695 					Hl7V2MessageBase messageImpl = (Hl7V2MessageBase) nextObj;
696 					messageImpl = messageImpl.asEncoding(myEncoding);
697 
698 					b.append(messageImpl.getSourceMessage());
699 					myMessages.set(i, messageImpl);
700 
701 				} else if (nextObj instanceof UnknownMessage) {
702 
703 					UnknownMessage messageImpl = (UnknownMessage) nextObj;
704 					b.append(messageImpl.getSourceMessage());
705 
706 				}
707 
708 				b.append("\r\r");
709 				int end = b.length();
710 				myMessageRanges.add(new Range(start, end));
711 			}
712 
713 			String oldValue = mySourceMessage;
714 			mySourceMessage = b.toString();
715 			firePropertyChange(SOURCE_MESSAGE_PROPERTY, oldValue, mySourceMessage);
716 
717 			if (StringUtils.equals(oldValue, mySourceMessage) == false) {
718 				setSaved(false);
719 			}
720 
721 			firePropertyChange(PROP_ENCODING, oldEncodingValue, myEncoding);
722 		}
723 	}
724 
725 	public void setHighlitedPathBasedOnRange(Range theRange) {
726 
727 		final String oldValue = myHighlitedPath;
728 
729 		if (theRange == null) {
730 			myHighlitedPath = null;
731 		} else {
732 			for (int i = 0; i < myMessageRanges.size(); i++) {
733 				Range range = myMessageRanges.get(i);
734 				if (range.contains(theRange.getStart())) {
735 					AbstractMessage<?> am = myMessages.get(i);
736 					if (am instanceof Hl7V2MessageBase) {
737 						Hl7V2MessageBase messageImpl = (Hl7V2MessageBase) am;
738 						messageImpl.setHighlitedPathBasedOnRange(theRange.add(-range.getStart()));
739 						myHighlitedPath = i + messageImpl.getHighlitedPath();
740 					}
741 					break;
742 				}
743 			}
744 		} // if-else
745 
746 		EventQueue.invokeLater(new Runnable() {
747 			public void run() {
748 				firePropertyChange(PROP_HIGHLITED_PATH, oldValue, myHighlitedPath);
749 				updateMessageDescription();
750 			}
751 		});
752 
753 	}
754 
755 	public void setHighlitedRangeBasedOnField(SegmentAndComponentPath thePath) {
756 		Segment segment = thePath.getSegment();
757 		Message message = segment.getMessage();
758 		int msgIndex = findSegmentMsgIndex(message);
759 
760 		Range oldValue = myHighlitedRange;
761 		if (msgIndex != -1) {
762 			Hl7V2MessageBase msg = (Hl7V2MessageBase) myMessages.get(msgIndex);
763 			msg.setHighlitedField(thePath);
764 			myHighlitedRange = msg.getHighlitedRange();
765 			if (myHighlitedRange != null) {
766 				myHighlitedRange = myHighlitedRange.add(myMessageRanges.get(msgIndex).getStart());
767 			} else {
768 				ourLog.info("No highlited range");
769 			}
770 		} else {
771 			ourLog.info("Couldn't find message");
772 		}
773 
774 		firePropertyChange(PROP_HIGHLITED_RANGE, oldValue, myHighlitedRange);
775 	}
776 
777 	public void setHighlitedRangeBasedOnSegment(Segment... theSegment) {
778 		Range oldValue = myHighlitedRange;
779 
780 		if (theSegment == null || theSegment.length == 0) {
781 			myHighlitedRange = null;
782 			firePropertyChange(PROP_HIGHLITED_RANGE, oldValue, myHighlitedRange);
783 			return;
784 		}
785 
786 		Message message = theSegment[0].getMessage();
787 		int msgIndex = findSegmentMsgIndex(message);
788 
789 		if (msgIndex != -1) {
790 			Hl7V2MessageBase msg = (Hl7V2MessageBase) myMessages.get(msgIndex);
791 			msg.setHighlitedRangeBasedOnSegment(theSegment);
792 			myHighlitedRange = msg.getHighlitedRange();
793 			if (myHighlitedRange != null) {
794 				myHighlitedRange = myHighlitedRange.add(myMessageRanges.get(msgIndex).getStart());
795 			} else {
796 				ourLog.info("No highlited range");
797 			}
798 		} else {
799 			ourLog.info("Couldn't find message");
800 		}
801 
802 		firePropertyChange(PROP_HIGHLITED_RANGE, oldValue, myHighlitedRange);
803 
804 	}
805 
806 	private void setId(String theId) {
807 		myId = theId;
808 	}
809 
810 	/**
811 	 * @param theLastSendToInterfaceId
812 	 *            the lastSendToInterfaceId to set
813 	 */
814 	public void setLastSendToInterfaceId(String theLastSendToInterfaceId) {
815 		myLastSendToInterfaceId = theLastSendToInterfaceId;
816 	}
817 
818 	public void setRuntimeProfile(ProfileGroup theProfileGroup) throws ProfileException {
819 		if (theProfileGroup == null && myRuntimeProfile == null && myValidationContext == null) {
820 			return;
821 		}
822 
823 		if (theProfileGroup == null && myRuntimeProfile == null && myValidationContext != null) {
824 			myValidationContext = null;
825 			myRuntimeProfile = null;
826 			myProfileId = null;
827 			firePropertyChange(PROP_VALIDATIONCONTEXT_OR_PROFILE, null, null);
828 			return;
829 		}
830 
831 		myRuntimeProfile = theProfileGroup;
832 		myValidationContext = null;
833 		myProfileId = theProfileGroup.getId();
834 
835 		for (AbstractMessage<?> next : myMessages) {
836 			if (next instanceof Hl7V2MessageBase) {
837 				((Hl7V2MessageBase) next).setRuntimeProfile(myRuntimeProfile);
838 			}
839 		}
840 
841 		firePropertyChange(PROP_VALIDATIONCONTEXT_OR_PROFILE, null, null);
842 	}
843 
844 	/**
845 	 * @param theSaveCharset
846 	 *            the saveCharset to set
847 	 */
848 	public void setSaveCharset(Charset theSaveCharset) {
849 		mySaveCharset = theSaveCharset;
850 	}
851 
852 	/**
853 	 * @param theSaved
854 	 *            the saved to set
855 	 */
856 	public void setSaved(boolean theSaved) {
857 		boolean oldValue = mySaved;
858 		mySaved = theSaved;
859 		firePropertyChange(SAVED_PROPERTY, oldValue, mySaved);
860 	}
861 
862 	/**
863 	 * @param theFileName
864 	 *            the fileName to set
865 	 */
866 	public void setSaveFileName(String theFileName) {
867 		String oldValue = null;
868 		mySaveFileName = theFileName;
869 		firePropertyChange(PROP_SAVE_FILENAME, oldValue, theFileName);
870 
871 		updateMessageDescription();
872 	}
873 
874 	/**
875 	 * @param theSaveFileTimestamp
876 	 *            the saveFileTimestamp to set
877 	 */
878 	public void setSaveFileTimestamp(long theSaveFileTimestamp) {
879 		mySaveFileTimestamp = theSaveFileTimestamp;
880 	}
881 
882 	public void setSaveLineEndings(LineEndingsEnum theLineEndings) {
883 		mySaveLineEndings = theLineEndings;
884 	}
885 
886 	public void setSaveStripComments(boolean theSaveComments) {
887 		myStripSaveComments = theSaveComments;
888 	}
889 
890 	/**
891 	 * @param theSendNumberOfTimes
892 	 *            the sendNumberOfTimes to set
893 	 */
894 	public void setSendNumberOfTimes(int theSendNumberOfTimes) {
895 		mySendNumberOfTimes = theSendNumberOfTimes;
896 	}
897 
898 	public void setSourceMessage(String theSourceMessage) {
899 		ourLog.info("About to set source message for collection");
900 
901 		ArrayList<AbstractMessage<?>> oldMessages = new ArrayList<AbstractMessage<?>>(myMessages);
902 		myMessages.clear();
903 		myMessageRanges.clear();
904 
905 		String message = fixLineEndings(theSourceMessage, LineEndingsEnum.HL7);
906 
907 		if (myEncoding == Hl7V2EncodingTypeEnum.ER_7) {
908 			doSetSourceMessageEr7(message);
909 		} else {
910 			doSetSourceMessageXml(message);
911 		}
912 
913 		String oldValue = mySourceMessage;
914 		mySourceMessage = theSourceMessage;
915 
916 		ourLog.info("Firing message change event");
917 
918 		firePropertyChange(PARSED_MESSAGES_PROPERTY, oldMessages, myMessages);
919 		firePropertyChange(SOURCE_MESSAGE_PROPERTY, oldValue, mySourceMessage);
920 
921 		if (StringUtils.equals(oldValue, mySourceMessage) == false) {
922 			setSaved(false);
923 		}
924 
925 		updateMessageDescription();
926 
927 		ourLog.info("Done setting source message for collection");
928 	}
929 
930 	/**
931 	 * @param theValidationContext
932 	 *            the validationContext to set
933 	 */
934 	public void setValidationContext(ValidationContext theValidationContext) {
935 		if (myValidationContext instanceof DefaultValidationpl/DefaultValidation.html#DefaultValidation">DefaultValidation && theValidationContext instanceof DefaultValidation) {
936 			return;
937 		}
938 
939 		if (theValidationContext != null) {
940 			myRuntimeProfile = null;
941 			myRuntimeProfile = null;
942 		}
943 
944 		ourLog.info("Setting validation context to: " + theValidationContext);
945 
946 		for (AbstractMessage<?> next : myMessages) {
947 			if (next instanceof Hl7V2MessageBase) {
948 				((Hl7V2MessageBase) next).setRuntimeProfile(null);
949 			}
950 		}
951 
952 		ValidationContext oldValue = myValidationContext;
953 		myValidationContext = theValidationContext;
954 		firePropertyChange(PROP_VALIDATIONCONTEXT_OR_PROFILE, oldValue, theValidationContext);
955 	}
956 
957 	/**
958 	 * {@inheritDoc}
959 	 */
960 	@Override
961 	public String toString() {
962 		return Hl7V2MessageCollection.class.getSimpleName() + "[" + getSaveFileName() + "]";
963 	}
964 
965 	private void updateMessageDescription() {
966 		String oldValue = myMessageDescription;
967 
968 		Matcher matcher = FIRSTLINE_COMMENT_PATTERN.matcher(StringUtils.defaultString(mySourceMessage));
969 		if (matcher.find()) {
970 
971 			myMessageDescription = matcher.group(1).trim();
972 
973 		} else if (mySaveFileName == null) {
974 
975 			int msgs = 0;
976 			AbstractMessage<?> firstNonComment = null;
977 			for (AbstractMessage<?> next : myMessages) {
978 				if (!(next instanceof Comment)) {
979 					if (msgs == 0) {
980 						firstNonComment = next;
981 					}
982 					msgs++;
983 				}
984 			}
985 
986 			if (msgs == 0) {
987 				myMessageDescription = "None";
988 			} else if (msgs == 1) {
989 				if (firstNonComment instanceof UnknownMessage) {
990 					myMessageDescription = "Unknown";
991 				} else if (firstNonComment instanceof Hl7V2MessageBase) {
992 					myMessageDescription = ((Hl7V2MessageBase) firstNonComment).getMessageDescription();
993 				} else {
994 					myMessageDescription = "None";
995 				}
996 			} else {
997 				myMessageDescription = msgs + " messages";
998 			}
999 
1000 		} else {
1001 
1002 			myMessageDescription = mySaveFileName.replaceAll(".*(\\\\|\\/)", "");
1003 
1004 		}
1005 
1006 		firePropertyChange(PROP_DESCRIPTION, oldValue, myMessageDescription);
1007 	}
1008 
1009 	public void updateSourceMessage(String theNewSource, int theChangeStart, int theChangeEnd) {
1010 		// TODO: optimize this to only update changed bits
1011 		setSourceMessage(theNewSource);
1012 	}
1013 
1014 	private synchronized void updateSourceMessageBasedOnEncodedMessage(int theMessageIndex, String theEncodedMessage) {
1015 		setHighlitedPathBasedOnRange(null);
1016 
1017 		Range range = myMessageRanges.get(theMessageIndex);
1018 
1019 		String oldValue = mySourceMessage;
1020 
1021 		String preMessage = mySourceMessage.substring(0, range.getStart());
1022 		String postMessage = mySourceMessage.length() > range.getEnd() ? mySourceMessage.substring(range.getEnd()) : "";
1023 		mySourceMessage = preMessage + theEncodedMessage + postMessage;
1024 
1025 		int sizeDifference = theEncodedMessage.length() - range.getDelta();
1026 		for (int i = theMessageIndex; i < myMessageRanges.size(); i++) {
1027 			if (i == theMessageIndex) {
1028 				myMessageRanges.set(i, myMessageRanges.get(i).addToEnd(sizeDifference));
1029 			} else {
1030 				myMessageRanges.set(i, myMessageRanges.get(i).addToBoth(sizeDifference));
1031 			}
1032 		}
1033 
1034 		Hl7V2MessageBase msgBase = (Hl7V2MessageBase) myMessages.get(theMessageIndex);
1035 		try {
1036 			msgBase.setSourceMessage(theEncodedMessage);
1037 		} catch (PropertyVetoException e) {
1038 			ourLog.error("Failed to update message", e);
1039 		}
1040 
1041 		firePropertyChange(SOURCE_MESSAGE_PROPERTY, oldValue, mySourceMessage);
1042 
1043 		if (StringUtils.equals(oldValue, mySourceMessage) == false) {
1044 			setSaved(false);
1045 		}
1046 
1047 	}
1048 
1049 	public void updateSourceMessageBasedOnParsedMessage(int theMessageIndex, Message theMsg) {
1050 		String encodedMessage;
1051 		try {
1052 			encodedMessage = myParser.encode(theMsg);
1053 		} catch (HL7Exception e) {
1054 			ourLog.error("Failed to encode message", e);
1055 			return;
1056 		}
1057 
1058 		updateSourceMessageBasedOnEncodedMessage(theMessageIndex, encodedMessage);
1059 	}
1060 
1061 	/**
1062 	 * Write the messages to an output stream
1063 	 */
1064 	public void writeToFile(Writer theWriter, boolean theSelectedSaveStripComments, LineEndingsEnum theSelectedLineEndings) throws IOException {
1065 		String toWrite = mySourceMessage;
1066 
1067 		if (theSelectedSaveStripComments) {
1068 			switch (myEncoding) {
1069 			case ER_7:
1070 
1071 				Pattern p = Pattern.compile("(^|\\r)\\s*#[^\\r]*");
1072 				Matcher m = p.matcher(toWrite);
1073 				toWrite = m.replaceAll("").trim() + "\r";
1074 
1075 				p = Pattern.compile("\\r\\s*\\r");
1076 				m = p.matcher(toWrite);
1077 				toWrite = m.replaceAll("\r");
1078 
1079 				break;
1080 			}
1081 		}
1082 
1083 		toWrite = fixLineEndings(toWrite, theSelectedLineEndings);
1084 
1085 		theWriter.append(toWrite);
1086 	}
1087 
1088 	public static Hl7V2MessageCollection fromXml(ProfileFileList theProfileFileList, String theContents) {
1089 		XmlFormat xmlFormat = JAXB.unmarshal(new StringReader(theContents),XmlFormat.class);
1090 		return fromXml(theProfileFileList, xmlFormat);
1091 	}
1092 
1093 	public static Hl7V2MessageCollection fromXml(ProfileFileList theProfileFileList, XmlFormat theXmlFormat) {
1094 
1095 		Hl7V2MessageCollection retVal = new Hl7V2MessageCollection();
1096 
1097 		retVal.setSaveFileName(isNotBlank(theXmlFormat.mySaveFileName) ? theXmlFormat.mySaveFileName : null);
1098 		retVal.setId(theXmlFormat.myId);
1099 		retVal.setLastSendToInterfaceId(theXmlFormat.myLastSendToInterfaceId);
1100 		retVal.setEditorShowMode(theXmlFormat.myEditorShowMode);
1101 		retVal.setSendNumberOfTimes(theXmlFormat.mySendNumberOfTimes);
1102 
1103 		try {
1104 			retVal.setSaveCharset(isNotEmpty(theXmlFormat.mySaveCharsetName) ? Charset.forName(theXmlFormat.mySaveCharsetName) : null);
1105 		} catch (IllegalCharsetNameException e) {
1106 			// ignore
1107 		}
1108 
1109 		if (isNotBlank(theXmlFormat.myValidationContextClass)) {
1110 			retVal.setValidationContext(ClassUtils.instantiateOrReturnNull(theXmlFormat.myValidationContextClass, ValidationContext.class));
1111 		}
1112 
1113 		if (isNotBlank(theXmlFormat.myProfileId)) {
1114 			try {
1115 				retVal.setRuntimeProfile(theProfileFileList.getProfile(theXmlFormat.myProfileId));
1116 			} catch (ProfileException e) {
1117 				ourLog.error("Failed to retrieve profile with id: " + theXmlFormat.myProfileId, e);
1118 			}
1119 		}
1120 
1121 		try {
1122 			retVal.setSaveLineEndings(LineEndingsEnum.valueOf(theXmlFormat.mySaveLineEndings));
1123 		} catch (Exception e) {
1124 			retVal.setSaveLineEndings(LineEndingsEnum.HL7);
1125 		}
1126 
1127 		retVal.setSaveStripComments(Boolean.parseBoolean(theXmlFormat.mySaveStripComments));
1128 
1129 		retVal.setEncoding("XML".equals(theXmlFormat.myEncodingType) ? Hl7V2EncodingTypeEnum.XML : Hl7V2EncodingTypeEnum.ER_7);
1130 
1131 		if (StringUtils.isNotBlank(theXmlFormat.mySourceMessage)) {
1132 			retVal.setSourceMessage(theXmlFormat.mySourceMessage);
1133 		} else {
1134 			retVal.setSourceMessage("");
1135 		}
1136 
1137 		retVal.setSaveFileTimestamp(theXmlFormat.mySaveTimestamp);
1138 
1139 		// set this last!
1140 		retVal.setSaved(theXmlFormat.mySaved);
1141 
1142 		return retVal;
1143 	}
1144 
1145 	public static void main(String[] args) {
1146 
1147 		System.out.println(Pattern.compile("<([A-Z0-9_]+)").matcher("<ORU_R01 xmlns=\"ffdf\">aaaaa").find());
1148 
1149 	}
1150 
1151 	private enum ParserState {
1152 		AT_MSG_START, COMMENT, IN_MSG, NONE, UNKNOWN
1153 	}
1154 
1155 	@XmlAccessorType(XmlAccessType.FIELD)
1156 	@XmlRootElement(name = "Hl7V2MessageCollection", namespace = "urn:ca.uhn.hapi:testpanel")
1157 	public static class XmlFormat {
1158 
1159 		@XmlAttribute(required = false, name = "editorShowMode")
1160 		public ShowEnum myEditorShowMode;
1161 
1162 		@XmlAttribute(required = true, name = "encodingType")
1163 		public String myEncodingType;
1164 
1165 		@XmlAttribute(required = true, name = "id")
1166 		public String myId;
1167 
1168 		@XmlAttribute(required = false, name = "lastSendToInterfaceId")
1169 		private String myLastSendToInterfaceId;
1170 
1171 		@XmlElement(required = true, name = "profileId")
1172 		public String myProfileId;
1173 
1174 		@XmlAttribute(required = true, name = "charsetName")
1175 		private String mySaveCharsetName;
1176 
1177 		@XmlAttribute(required = true, name = "saved")
1178 		private boolean mySaved;
1179 
1180 		@XmlAttribute(required = true, name = "saveFileName")
1181 		private String mySaveFileName;
1182 
1183 		@XmlAttribute(required = true, name = "saveLineEndings")
1184 		private String mySaveLineEndings;
1185 
1186 		@XmlAttribute(required = true, name = "saveStripComments")
1187 		private String mySaveStripComments;
1188 
1189 		@XmlAttribute(required = true, name = "saveFileTimestamp")
1190 		private long mySaveTimestamp;
1191 
1192 		@XmlAttribute(required = false, name = "sendNumberOfTimes")
1193 		public int mySendNumberOfTimes;
1194 
1195 		@XmlElement(required = true, name = "sourceMessage")
1196 		public String mySourceMessage;
1197 
1198 		@XmlElement(required = true, name = "validationContextClass")
1199 		public String myValidationContextClass;
1200 
1201 	}
1202 
1203 }