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 javax.xml.bind.JAXB;
46  import javax.xml.bind.annotation.XmlAccessType;
47  import javax.xml.bind.annotation.XmlAccessorType;
48  import javax.xml.bind.annotation.XmlAttribute;
49  import javax.xml.bind.annotation.XmlElement;
50  import javax.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 			@Override
748 			public void run() {
749 				firePropertyChange(PROP_HIGHLITED_PATH, oldValue, myHighlitedPath);
750 				updateMessageDescription();
751 			}
752 		});
753 
754 	}
755 
756 	public void setHighlitedRangeBasedOnField(SegmentAndComponentPath thePath) {
757 		Segment segment = thePath.getSegment();
758 		Message message = segment.getMessage();
759 		int msgIndex = findSegmentMsgIndex(message);
760 
761 		Range oldValue = myHighlitedRange;
762 		if (msgIndex != -1) {
763 			Hl7V2MessageBase msg = (Hl7V2MessageBase) myMessages.get(msgIndex);
764 			msg.setHighlitedField(thePath);
765 			myHighlitedRange = msg.getHighlitedRange();
766 			if (myHighlitedRange != null) {
767 				myHighlitedRange = myHighlitedRange.add(myMessageRanges.get(msgIndex).getStart());
768 			} else {
769 				ourLog.info("No highlited range");
770 			}
771 		} else {
772 			ourLog.info("Couldn't find message");
773 		}
774 
775 		firePropertyChange(PROP_HIGHLITED_RANGE, oldValue, myHighlitedRange);
776 	}
777 
778 	public void setHighlitedRangeBasedOnSegment(Segment... theSegment) {
779 		Range oldValue = myHighlitedRange;
780 
781 		if (theSegment == null || theSegment.length == 0) {
782 			myHighlitedRange = null;
783 			firePropertyChange(PROP_HIGHLITED_RANGE, oldValue, myHighlitedRange);
784 			return;
785 		}
786 
787 		Message message = theSegment[0].getMessage();
788 		int msgIndex = findSegmentMsgIndex(message);
789 
790 		if (msgIndex != -1) {
791 			Hl7V2MessageBase msg = (Hl7V2MessageBase) myMessages.get(msgIndex);
792 			msg.setHighlitedRangeBasedOnSegment(theSegment);
793 			myHighlitedRange = msg.getHighlitedRange();
794 			if (myHighlitedRange != null) {
795 				myHighlitedRange = myHighlitedRange.add(myMessageRanges.get(msgIndex).getStart());
796 			} else {
797 				ourLog.info("No highlited range");
798 			}
799 		} else {
800 			ourLog.info("Couldn't find message");
801 		}
802 
803 		firePropertyChange(PROP_HIGHLITED_RANGE, oldValue, myHighlitedRange);
804 
805 	}
806 
807 	private void setId(String theId) {
808 		myId = theId;
809 	}
810 
811 	/**
812 	 * @param theLastSendToInterfaceId
813 	 *            the lastSendToInterfaceId to set
814 	 */
815 	public void setLastSendToInterfaceId(String theLastSendToInterfaceId) {
816 		myLastSendToInterfaceId = theLastSendToInterfaceId;
817 	}
818 
819 	public void setRuntimeProfile(ProfileGroup theProfileGroup) throws ProfileException {
820 		if (theProfileGroup == null && myRuntimeProfile == null && myValidationContext == null) {
821 			return;
822 		}
823 
824 		if (theProfileGroup == null && myRuntimeProfile == null && myValidationContext != null) {
825 			myValidationContext = null;
826 			myRuntimeProfile = null;
827 			myProfileId = null;
828 			firePropertyChange(PROP_VALIDATIONCONTEXT_OR_PROFILE, null, null);
829 			return;
830 		}
831 
832 		myRuntimeProfile = theProfileGroup;
833 		myValidationContext = null;
834 		myProfileId = theProfileGroup.getId();
835 
836 		for (AbstractMessage<?> next : myMessages) {
837 			if (next instanceof Hl7V2MessageBase) {
838 				((Hl7V2MessageBase) next).setRuntimeProfile(myRuntimeProfile);
839 			}
840 		}
841 
842 		firePropertyChange(PROP_VALIDATIONCONTEXT_OR_PROFILE, null, null);
843 	}
844 
845 	/**
846 	 * @param theSaveCharset
847 	 *            the saveCharset to set
848 	 */
849 	public void setSaveCharset(Charset theSaveCharset) {
850 		mySaveCharset = theSaveCharset;
851 	}
852 
853 	/**
854 	 * @param theSaved
855 	 *            the saved to set
856 	 */
857 	public void setSaved(boolean theSaved) {
858 		boolean oldValue = mySaved;
859 		mySaved = theSaved;
860 		firePropertyChange(SAVED_PROPERTY, oldValue, mySaved);
861 	}
862 
863 	/**
864 	 * @param theFileName
865 	 *            the fileName to set
866 	 */
867 	public void setSaveFileName(String theFileName) {
868 		String oldValue = null;
869 		mySaveFileName = theFileName;
870 		firePropertyChange(PROP_SAVE_FILENAME, oldValue, theFileName);
871 
872 		updateMessageDescription();
873 	}
874 
875 	/**
876 	 * @param theSaveFileTimestamp
877 	 *            the saveFileTimestamp to set
878 	 */
879 	public void setSaveFileTimestamp(long theSaveFileTimestamp) {
880 		mySaveFileTimestamp = theSaveFileTimestamp;
881 	}
882 
883 	public void setSaveLineEndings(LineEndingsEnum theLineEndings) {
884 		mySaveLineEndings = theLineEndings;
885 	}
886 
887 	public void setSaveStripComments(boolean theSaveComments) {
888 		myStripSaveComments = theSaveComments;
889 	}
890 
891 	/**
892 	 * @param theSendNumberOfTimes
893 	 *            the sendNumberOfTimes to set
894 	 */
895 	public void setSendNumberOfTimes(int theSendNumberOfTimes) {
896 		mySendNumberOfTimes = theSendNumberOfTimes;
897 	}
898 
899 	public void setSourceMessage(String theSourceMessage) {
900 		ourLog.info("About to set source message for collection");
901 
902 		ArrayList<AbstractMessage<?>> oldMessages = new ArrayList<AbstractMessage<?>>(myMessages);
903 		myMessages.clear();
904 		myMessageRanges.clear();
905 
906 		String message = fixLineEndings(theSourceMessage, LineEndingsEnum.HL7);
907 
908 		if (myEncoding == Hl7V2EncodingTypeEnum.ER_7) {
909 			doSetSourceMessageEr7(message);
910 		} else {
911 			doSetSourceMessageXml(message);
912 		}
913 
914 		String oldValue = mySourceMessage;
915 		mySourceMessage = theSourceMessage;
916 
917 		ourLog.info("Firing message change event");
918 
919 		firePropertyChange(PARSED_MESSAGES_PROPERTY, oldMessages, myMessages);
920 		firePropertyChange(SOURCE_MESSAGE_PROPERTY, oldValue, mySourceMessage);
921 
922 		if (StringUtils.equals(oldValue, mySourceMessage) == false) {
923 			setSaved(false);
924 		}
925 
926 		updateMessageDescription();
927 
928 		ourLog.info("Done setting source message for collection");
929 	}
930 
931 	/**
932 	 * @param theValidationContext
933 	 *            the validationContext to set
934 	 */
935 	public void setValidationContext(ValidationContext theValidationContext) {
936 		if (myValidationContext instanceof DefaultValidation && theValidationContext instanceof DefaultValidation) {
937 			return;
938 		}
939 
940 		if (theValidationContext != null) {
941 			myRuntimeProfile = null;
942 			myRuntimeProfile = null;
943 		}
944 
945 		ourLog.info("Setting validation context to: " + theValidationContext);
946 
947 		for (AbstractMessage<?> next : myMessages) {
948 			if (next instanceof Hl7V2MessageBase) {
949 				((Hl7V2MessageBase) next).setRuntimeProfile(null);
950 			}
951 		}
952 
953 		ValidationContext oldValue = myValidationContext;
954 		myValidationContext = theValidationContext;
955 		firePropertyChange(PROP_VALIDATIONCONTEXT_OR_PROFILE, oldValue, theValidationContext);
956 	}
957 
958 	/**
959 	 * {@inheritDoc}
960 	 */
961 	@Override
962 	public String toString() {
963 		return Hl7V2MessageCollection.class.getSimpleName() + "[" + getSaveFileName() + "]";
964 	}
965 
966 	private void updateMessageDescription() {
967 		String oldValue = myMessageDescription;
968 
969 		Matcher matcher = FIRSTLINE_COMMENT_PATTERN.matcher(StringUtils.defaultString(mySourceMessage));
970 		if (matcher.find()) {
971 
972 			myMessageDescription = matcher.group(1).trim();
973 
974 		} else if (mySaveFileName == null) {
975 
976 			int msgs = 0;
977 			AbstractMessage<?> firstNonComment = null;
978 			for (AbstractMessage<?> next : myMessages) {
979 				if (!(next instanceof Comment)) {
980 					if (msgs == 0) {
981 						firstNonComment = next;
982 					}
983 					msgs++;
984 				}
985 			}
986 
987 			if (msgs == 0) {
988 				myMessageDescription = "None";
989 			} else if (msgs == 1) {
990 				if (firstNonComment instanceof UnknownMessage) {
991 					myMessageDescription = "Unknown";
992 				} else if (firstNonComment instanceof Hl7V2MessageBase) {
993 					myMessageDescription = ((Hl7V2MessageBase) firstNonComment).getMessageDescription();
994 				} else {
995 					myMessageDescription = "None";
996 				}
997 			} else {
998 				myMessageDescription = msgs + " messages";
999 			}
1000 
1001 		} else {
1002 
1003 			myMessageDescription = mySaveFileName.replaceAll(".*(\\\\|\\/)", "");
1004 
1005 		}
1006 
1007 		firePropertyChange(PROP_DESCRIPTION, oldValue, myMessageDescription);
1008 	}
1009 
1010 	public void updateSourceMessage(String theNewSource, int theChangeStart, int theChangeEnd) {
1011 		// TODO: optimize this to only update changed bits
1012 		setSourceMessage(theNewSource);
1013 	}
1014 
1015 	private synchronized void updateSourceMessageBasedOnEncodedMessage(int theMessageIndex, String theEncodedMessage) {
1016 		setHighlitedPathBasedOnRange(null);
1017 
1018 		Range range = myMessageRanges.get(theMessageIndex);
1019 
1020 		String oldValue = mySourceMessage;
1021 
1022 		String preMessage = mySourceMessage.substring(0, range.getStart());
1023 		String postMessage = mySourceMessage.length() > range.getEnd() ? mySourceMessage.substring(range.getEnd()) : "";
1024 		mySourceMessage = preMessage + theEncodedMessage + postMessage;
1025 
1026 		int sizeDifference = theEncodedMessage.length() - range.getDelta();
1027 		for (int i = theMessageIndex; i < myMessageRanges.size(); i++) {
1028 			if (i == theMessageIndex) {
1029 				myMessageRanges.set(i, myMessageRanges.get(i).addToEnd(sizeDifference));
1030 			} else {
1031 				myMessageRanges.set(i, myMessageRanges.get(i).addToBoth(sizeDifference));
1032 			}
1033 		}
1034 
1035 		Hl7V2MessageBase msgBase = (Hl7V2MessageBase) myMessages.get(theMessageIndex);
1036 		try {
1037 			msgBase.setSourceMessage(theEncodedMessage);
1038 		} catch (PropertyVetoException e) {
1039 			ourLog.error("Failed to update message", e);
1040 		}
1041 
1042 		firePropertyChange(SOURCE_MESSAGE_PROPERTY, oldValue, mySourceMessage);
1043 
1044 		if (StringUtils.equals(oldValue, mySourceMessage) == false) {
1045 			setSaved(false);
1046 		}
1047 
1048 	}
1049 
1050 	public void updateSourceMessageBasedOnParsedMessage(int theMessageIndex, Message theMsg) {
1051 		String encodedMessage;
1052 		try {
1053 			encodedMessage = myParser.encode(theMsg);
1054 		} catch (HL7Exception e) {
1055 			ourLog.error("Failed to encode message", e);
1056 			return;
1057 		}
1058 
1059 		updateSourceMessageBasedOnEncodedMessage(theMessageIndex, encodedMessage);
1060 	}
1061 
1062 	/**
1063 	 * Write the messages to an output stream
1064 	 */
1065 	public void writeToFile(Writer theWriter, boolean theSelectedSaveStripComments, LineEndingsEnum theSelectedLineEndings) throws IOException {
1066 		String toWrite = mySourceMessage;
1067 
1068 		if (theSelectedSaveStripComments) {
1069 			switch (myEncoding) {
1070 			case ER_7:
1071 
1072 				Pattern p = Pattern.compile("(^|\\r)\\s*#[^\\r]*");
1073 				Matcher m = p.matcher(toWrite);
1074 				toWrite = m.replaceAll("").trim() + "\r";
1075 
1076 				p = Pattern.compile("\\r\\s*\\r");
1077 				m = p.matcher(toWrite);
1078 				toWrite = m.replaceAll("\r");
1079 
1080 				break;
1081 			}
1082 		}
1083 
1084 		toWrite = fixLineEndings(toWrite, theSelectedLineEndings);
1085 
1086 		theWriter.append(toWrite);
1087 	}
1088 
1089 	public static Hl7V2MessageCollection fromXml(ProfileFileList theProfileFileList, String theContents) {
1090 		XmlFormat xmlFormat = JAXB.unmarshal(new StringReader(theContents),XmlFormat.class);
1091 		return fromXml(theProfileFileList, xmlFormat);
1092 	}
1093 
1094 	public static Hl7V2MessageCollection fromXml(ProfileFileList theProfileFileList, XmlFormat theXmlFormat) {
1095 
1096 		Hl7V2MessageCollection retVal = new Hl7V2MessageCollection();
1097 
1098 		retVal.setSaveFileName(isNotBlank(theXmlFormat.mySaveFileName) ? theXmlFormat.mySaveFileName : null);
1099 		retVal.setId(theXmlFormat.myId);
1100 		retVal.setLastSendToInterfaceId(theXmlFormat.myLastSendToInterfaceId);
1101 		retVal.setEditorShowMode(theXmlFormat.myEditorShowMode);
1102 		retVal.setSendNumberOfTimes(theXmlFormat.mySendNumberOfTimes);
1103 
1104 		try {
1105 			retVal.setSaveCharset(isNotEmpty(theXmlFormat.mySaveCharsetName) ? Charset.forName(theXmlFormat.mySaveCharsetName) : null);
1106 		} catch (IllegalCharsetNameException e) {
1107 			// ignore
1108 		}
1109 
1110 		if (isNotBlank(theXmlFormat.myValidationContextClass)) {
1111 			retVal.setValidationContext(ClassUtils.instantiateOrReturnNull(theXmlFormat.myValidationContextClass, ValidationContext.class));
1112 		}
1113 
1114 		if (isNotBlank(theXmlFormat.myProfileId)) {
1115 			try {
1116 				retVal.setRuntimeProfile(theProfileFileList.getProfile(theXmlFormat.myProfileId));
1117 			} catch (ProfileException e) {
1118 				ourLog.error("Failed to retrieve profile with id: " + theXmlFormat.myProfileId, e);
1119 			}
1120 		}
1121 
1122 		try {
1123 			retVal.setSaveLineEndings(LineEndingsEnum.valueOf(theXmlFormat.mySaveLineEndings));
1124 		} catch (Exception e) {
1125 			retVal.setSaveLineEndings(LineEndingsEnum.HL7);
1126 		}
1127 
1128 		retVal.setSaveStripComments(Boolean.parseBoolean(theXmlFormat.mySaveStripComments));
1129 
1130 		retVal.setEncoding("XML".equals(theXmlFormat.myEncodingType) ? Hl7V2EncodingTypeEnum.XML : Hl7V2EncodingTypeEnum.ER_7);
1131 
1132 		if (StringUtils.isNotBlank(theXmlFormat.mySourceMessage)) {
1133 			retVal.setSourceMessage(theXmlFormat.mySourceMessage);
1134 		} else {
1135 			retVal.setSourceMessage("");
1136 		}
1137 
1138 		retVal.setSaveFileTimestamp(theXmlFormat.mySaveTimestamp);
1139 
1140 		// set this last!
1141 		retVal.setSaved(theXmlFormat.mySaved);
1142 
1143 		return retVal;
1144 	}
1145 
1146 	public static void main(String[] args) {
1147 
1148 		System.out.println(Pattern.compile("<([A-Z0-9_]+)").matcher("<ORU_R01 xmlns=\"ffdf\">aaaaa").find());
1149 
1150 	}
1151 
1152 	private enum ParserState {
1153 		AT_MSG_START, COMMENT, IN_MSG, NONE, UNKNOWN
1154 	}
1155 
1156 	@XmlAccessorType(XmlAccessType.FIELD)
1157 	@XmlRootElement(name = "Hl7V2MessageCollection", namespace = "urn:ca.uhn.hapi:testpanel")
1158 	public static class XmlFormat {
1159 
1160 		@XmlAttribute(required = false, name = "editorShowMode")
1161 		public ShowEnum myEditorShowMode;
1162 
1163 		@XmlAttribute(required = true, name = "encodingType")
1164 		public String myEncodingType;
1165 
1166 		@XmlAttribute(required = true, name = "id")
1167 		public String myId;
1168 
1169 		@XmlAttribute(required = false, name = "lastSendToInterfaceId")
1170 		private String myLastSendToInterfaceId;
1171 
1172 		@XmlElement(required = true, name = "profileId")
1173 		public String myProfileId;
1174 
1175 		@XmlAttribute(required = true, name = "charsetName")
1176 		private String mySaveCharsetName;
1177 
1178 		@XmlAttribute(required = true, name = "saved")
1179 		private boolean mySaved;
1180 
1181 		@XmlAttribute(required = true, name = "saveFileName")
1182 		private String mySaveFileName;
1183 
1184 		@XmlAttribute(required = true, name = "saveLineEndings")
1185 		private String mySaveLineEndings;
1186 
1187 		@XmlAttribute(required = true, name = "saveStripComments")
1188 		private String mySaveStripComments;
1189 
1190 		@XmlAttribute(required = true, name = "saveFileTimestamp")
1191 		private long mySaveTimestamp;
1192 
1193 		@XmlAttribute(required = false, name = "sendNumberOfTimes")
1194 		public int mySendNumberOfTimes;
1195 
1196 		@XmlElement(required = true, name = "sourceMessage")
1197 		public String mySourceMessage;
1198 
1199 		@XmlElement(required = true, name = "validationContextClass")
1200 		public String myValidationContextClass;
1201 
1202 	}
1203 
1204 }