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 "PipeParser.java".  Description:
10   * "An implementation of Parser that supports traditionally encoded (i.e"
11   *
12   * The Initial Developer of the Original Code is University Health Network. Copyright (C)
13   * 2001.  All Rights Reserved.
14   *
15   * Contributor(s): Kenneth Beaton.
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   */
27  
28  package ca.uhn.hl7v2.parser;
29  
30  import java.util.ArrayList;
31  import java.util.List;
32  import java.util.StringTokenizer;
33  
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  import ca.uhn.hl7v2.ErrorCode;
38  import ca.uhn.hl7v2.HL7Exception;
39  import ca.uhn.hl7v2.Version;
40  import ca.uhn.hl7v2.model.Group;
41  import ca.uhn.hl7v2.model.Message;
42  import ca.uhn.hl7v2.model.Primitive;
43  import ca.uhn.hl7v2.model.Segment;
44  import ca.uhn.hl7v2.model.Structure;
45  import ca.uhn.hl7v2.model.Type;
46  import ca.uhn.hl7v2.model.Varies;
47  import ca.uhn.hl7v2.util.FilterIterator;
48  import ca.uhn.hl7v2.util.MessageIterator;
49  import ca.uhn.hl7v2.util.Terser;
50  
51  /**
52   * This is a legacy implementation of the PipeParser and should not be used
53   * for new projects.
54   *
55   * In version 1.0 of HAPI, a behaviour was corrected where unexpected segments
56   * would be placed at the tail end of the first segment group encountered. Any
57   * legacy code which still depends on previous behaviour can use this
58   * implementation.
59   *
60   * @author Bryan Tripp (bryan_tripp@sourceforge.net)
61   * @deprecated
62   */
63  class OldPipeParser extends Parser {
64      
65      private static final Logger log = LoggerFactory.getLogger(OldPipeParser.class);
66      
67      private final static String segDelim = "\r"; //see section 2.8 of spec
68      
69      /** Creates a new PipeParser */
70      public OldPipeParser() {
71      }
72  
73      /** 
74       * Creates a new PipeParser 
75       *  
76       * @param theFactory custom factory to use for model class lookup 
77       */
78      public OldPipeParser(ModelClassFactory theFactory) {
79      	super(theFactory);
80      }
81      
82      /**
83       * Returns a String representing the encoding of the given message, if
84       * the encoding is recognized.  For example if the given message appears
85       * to be encoded using HL7 2.x XML rules then "XML" would be returned.
86       * If the encoding is not recognized then null is returned.  That this
87       * method returns a specific encoding does not guarantee that the
88       * message is correctly encoded (e.g. well formed XML) - just that
89       * it is not encoded using any other encoding than the one returned.
90       */
91      public String getEncoding(String message) {
92          String encoding = null;
93          
94          //quit if the string is too short
95          if (message.length() < 4)
96              return null;
97          
98          //see if it looks like this message is | encoded ...
99          boolean ok = true;
100         
101         //string should start with "MSH"
102         if (!message.startsWith("MSH"))
103             return null;
104         
105         //4th character of each segment should be field delimiter
106         char fourthChar = message.charAt(3);
107         StringTokenizer st = new StringTokenizer(message, String.valueOf(segDelim), false);
108         while (st.hasMoreTokens()) {
109             String x = st.nextToken();
110             if (x.length() > 0) {
111                 if (Character.isWhitespace(x.charAt(0)))
112                     x = stripLeadingWhitespace(x);
113                 if (x.length() >= 4 && x.charAt(3) != fourthChar)
114                     return null;
115             }
116         }
117         
118         //should be at least 11 field delimiters (because MSH-12 is required)
119         int nextFieldDelimLoc = 0;
120         for (int i = 0; i < 11; i++) {
121             nextFieldDelimLoc = message.indexOf(fourthChar, nextFieldDelimLoc + 1);
122             if (nextFieldDelimLoc < 0)
123                 return null;
124         }
125         
126         if (ok)
127             encoding = "VB";
128         
129         return encoding;
130     }
131     
132     /**
133      * @return the preferred encoding of this Parser
134      */
135     public String getDefaultEncoding() {
136         return "VB";
137     }
138     
139     /**
140      * Returns true if and only if the given encoding is supported
141      * by this Parser.
142      */
143     public boolean supportsEncoding(String encoding) {
144         boolean supports = false;
145         if (encoding != null && encoding.equals("VB"))
146             supports = true;
147         return supports;
148     }
149     
150     /**
151      * @deprecated this method should not be public 
152      * @param message
153      * @return
154      * @throws HL7Exception
155      * @throws EncodingNotSupportedException
156      */
157     public String getMessageStructure(String message) throws HL7Exception, EncodingNotSupportedException {
158         return getStructure(message).messageStructure;
159     }
160     
161     /**
162      * @returns the message structure from MSH-9-3
163      */
164     private MessageStructure getStructure(String message) throws HL7Exception, EncodingNotSupportedException {
165         EncodingCharacters ec = getEncodingChars(message);
166         String messageStructure = null;
167         boolean explicityDefined = true;
168         String wholeFieldNine;
169         try {
170             String[] fields = split(message.substring(0, Math.max(message.indexOf(segDelim), message.length())),
171                 String.valueOf(ec.getFieldSeparator()));
172             wholeFieldNine = fields[8];
173             
174             //message structure is component 3 but we'll accept a composite of 1 and 2 if there is no component 3 ...
175             //      if component 1 is ACK, then the structure is ACK regardless of component 2
176             String[] comps = split(wholeFieldNine, String.valueOf(ec.getComponentSeparator()));
177             if (comps.length >= 3) {
178                 messageStructure = comps[2];
179             } else if (comps.length > 0 && comps[0] != null && comps[0].equals("ACK")) {
180                 messageStructure = "ACK";
181             } else if (comps.length == 2) {
182                 explicityDefined = false;
183                 messageStructure = comps[0] + "_" + comps[1];
184             }
185             /*else if (comps.length == 1 && comps[0] != null && comps[0].equals("ACK")) {
186                 messageStructure = "ACK"; //it's common for people to only populate component 1 in an ACK msg
187             }*/
188             else {
189                 StringBuffer buf = new StringBuffer("Can't determine message structure from MSH-9: ");
190                 buf.append(wholeFieldNine);
191                 if (comps.length < 3) {
192                     buf.append(" HINT: there are only ");
193                     buf.append(comps.length);
194                     buf.append(" of 3 components present");
195                 }
196                 throw new HL7Exception(buf.toString(), ErrorCode.UNSUPPORTED_MESSAGE_TYPE);
197             }            
198         }
199         catch (IndexOutOfBoundsException e) {
200             throw new HL7Exception(
201             "Can't find message structure (MSH-9-3): " + e.getMessage(),
202             ErrorCode.UNSUPPORTED_MESSAGE_TYPE);
203         }
204         
205         return new MessageStructure(messageStructure, explicityDefined);
206     }
207     
208     /**
209      * Returns object that contains the field separator and encoding characters
210      * for this message.
211      */
212     private static EncodingCharacters getEncodingChars(String message) {
213         return new EncodingCharacters(message.charAt(3), message.substring(4, 8));
214     }
215     
216     /**
217      * Parses a message string and returns the corresponding Message
218      * object.  Unexpected segments added at the end of their group.  
219      *
220      * @throws HL7Exception if the message is not correctly formatted.
221      * @throws EncodingNotSupportedException if the message encoded
222      *      is not supported by this parser.
223      */
224     protected Message doParse(String message, String version) throws HL7Exception, EncodingNotSupportedException {
225         
226         //try to instantiate a message object of the right class
227         MessageStructure structure = getStructure(message);
228         Message m = instantiateMessage(structure.messageStructure, version, structure.explicitlyDefined);
229 
230         parse(m, message);
231 
232         return m;
233     }
234     
235     /**
236      * Parses a segment string and populates the given Segment object.  Unexpected fields are
237      * added as Varies' at the end of the segment.  
238      *
239      * @throws HL7Exception if the given string does not contain the
240      *      given segment or if the string is not encoded properly
241      */
242     public void parse(Segment destination, String segment, EncodingCharacters encodingChars) throws HL7Exception {
243         int fieldOffset = 0;
244         if (isDelimDefSegment(destination.getName())) {
245             fieldOffset = 1;
246             //set field 1 to fourth character of string
247             Terser.set(destination, 1, 0, 1, 1, String.valueOf(encodingChars.getFieldSeparator()));
248         }
249         
250         String[] fields = split(segment, String.valueOf(encodingChars.getFieldSeparator()));
251         //destination.setName(fields[0]);
252         for (int i = 1; i < fields.length; i++) {
253             String[] reps = split(fields[i], String.valueOf(encodingChars.getRepetitionSeparator()));
254             log.debug("{} reps delimited by: {}", reps.length, encodingChars.getRepetitionSeparator());                
255             
256             //MSH-2 will get split incorrectly so we have to fudge it ...
257             boolean isMSH2 = isDelimDefSegment(destination.getName()) && i+fieldOffset == 2;
258             if (isMSH2) {  
259                 reps = new String[1];
260                 reps[0] = fields[i];
261             }
262             
263             for (int j = 0; j < reps.length; j++) {
264                 try {
265                     StringBuffer statusMessage = new StringBuffer("Parsing field ");
266                     statusMessage.append(i+fieldOffset);
267                     statusMessage.append(" repetition ");
268                     statusMessage.append(j);
269                     log.debug(statusMessage.toString());
270                     //parse(destination.getField(i + fieldOffset, j), reps[j], encodingChars, false);
271 
272                     Type field = destination.getField(i + fieldOffset, j);
273                     if (isMSH2) {
274                         Terser.getPrimitive(field, 1, 1).setValue(reps[j]);
275                     } else {
276                         parse(field, reps[j], encodingChars);
277                     }
278                 }
279                 catch (HL7Exception e) {
280                     //set the field location and throw again ...
281                     e.setFieldPosition(i);
282                     e.setSegmentRepetition(MessageIterator.getIndex(destination.getParent(), destination).rep);
283                     e.setSegmentName(destination.getName());
284                     throw e;
285                 }
286             }
287         }
288         
289         //set data type of OBX-5
290         if (destination.getClass().getName().indexOf("OBX") >= 0) {
291             Varies.fixOBX5(destination, getFactory(), getHapiContext().getParserConfiguration());
292         }
293         
294     }
295     
296     /** 
297      * @return true if the segment is MSH, FHS, or BHS.  These need special treatment 
298      *  because they define delimiters.
299      * @param theSegmentName
300      */
301     private static boolean isDelimDefSegment(String theSegmentName) {
302         boolean is = false;
303         if (theSegmentName.equals("MSH") 
304             || theSegmentName.equals("FHS") 
305             || theSegmentName.equals("BHS")) 
306         {
307             is = true;
308         }
309         return is;
310     }
311     
312     /**
313      * Fills a field with values from an unparsed string representing the field.  
314      * @param destinationField the field Type
315      * @param data the field string (including all components and subcomponents; not including field delimiters)
316      * @param encodingCharacters the encoding characters used in the message
317      */
318     public void parse(Type destinationField, String data, EncodingCharacters encodingCharacters) throws HL7Exception {
319         String[] components = split(data, String.valueOf(encodingCharacters.getComponentSeparator()));
320         for (int i = 0; i < components.length; i++) {
321             String[] subcomponents = split(components[i], String.valueOf(encodingCharacters.getSubcomponentSeparator()));
322             for (int j = 0; j < subcomponents.length; j++) {
323                 String val = subcomponents[j];
324                 if (val != null) {
325                     val = Escape.unescape(val, encodingCharacters);
326                 }
327                 Terser.getPrimitive(destinationField, i+1, j+1).setValue(val);                
328             }
329         }
330     }
331     
332     /**
333      * Splits the given composite string into an array of components using the given
334      * delimiter.
335      */
336     public static String[] split(String composite, String delim) {
337         List<String> components = new ArrayList<String>();
338         
339         //defend against evil nulls
340         if (composite == null)
341             composite = "";
342         if (delim == null)
343             delim = "";
344         
345         StringTokenizer tok = new StringTokenizer(composite, delim, true);
346         boolean previousTokenWasDelim = true;
347         while (tok.hasMoreTokens()) {
348             String thisTok = tok.nextToken();
349             if (thisTok.equals(delim)) {
350                 if (previousTokenWasDelim)
351                     components.add(null);
352                 previousTokenWasDelim = true;
353             }
354             else {
355                 components.add(thisTok);
356                 previousTokenWasDelim = false;
357             }
358         }
359         
360         return components.toArray(new String[components.size()]);
361     }
362     
363     /**
364      * Encodes the given Type, using the given encoding characters. 
365      * It is assumed that the Type represents a complete field rather than a component.
366      */
367     public static String encode(Type source, EncodingCharacters encodingChars) {
368         StringBuffer field = new StringBuffer();
369         for (int i = 1; i <= Terser.numComponents(source); i++) {
370             StringBuffer comp = new StringBuffer();
371             for (int j = 1; j <= Terser.numSubComponents(source, i); j++) {
372                 Primitive p = Terser.getPrimitive(source, i, j);
373                 comp.append(encodePrimitive(p, encodingChars));
374                 comp.append(encodingChars.getSubcomponentSeparator());
375             }
376             field.append(stripExtraDelimiters(comp.toString(), encodingChars.getSubcomponentSeparator()));
377             field.append(encodingChars.getComponentSeparator());
378         }
379         return stripExtraDelimiters(field.toString(), encodingChars.getComponentSeparator());
380         //return encode(source, encodingChars, false);
381     }
382     
383     private static String encodePrimitive(Primitive p, EncodingCharacters encodingChars) {
384         String val = ((Primitive) p).getValue();
385         if (val == null) {
386             val = "";
387         } else {
388             val = Escape.escape(val, encodingChars);
389         }
390         return val;
391     }
392     
393     /**
394      * Removes unecessary delimiters from the end of a field or segment.
395      * This seems to be more convenient than checking to see if they are needed
396      * while we are building the encoded string.
397      */
398     private static String stripExtraDelimiters(String in, char delim) {
399         char[] chars = in.toCharArray();
400         
401         //search from back end for first occurance of non-delimiter ...
402         int c = chars.length - 1;
403         boolean found = false;
404         while (c >= 0 && !found) {
405             if (chars[c--] != delim)
406                 found = true;
407         }
408         
409         String ret = "";
410         if (found)
411             ret = String.valueOf(chars, 0, c + 2);
412         return ret;
413     }
414     
415     /**
416      * Formats a Message object into an HL7 message string using the given
417      * encoding.
418      * @throws HL7Exception if the data fields in the message do not permit encoding
419      *      (e.g. required fields are null)
420      * @throws EncodingNotSupportedException if the requested encoding is not
421      *      supported by this parser.
422      */
423     protected String doEncode(Message source, String encoding) throws HL7Exception, EncodingNotSupportedException {
424         if (!this.supportsEncoding(encoding))
425             throw new EncodingNotSupportedException("This parser does not support the " + encoding + " encoding");
426         
427         return encode(source);
428     }
429     
430     /**
431      * Formats a Message object into an HL7 message string using this parser's
432      * default encoding ("VB").
433      * @throws HL7Exception if the data fields in the message do not permit encoding
434      *      (e.g. required fields are null)
435      */
436     protected String doEncode(Message source) throws HL7Exception {
437         //get encoding characters ...
438         Segment msh = (Segment) source.get("MSH");
439         String fieldSepString = Terser.get(msh, 1, 0, 1, 1);
440         
441         if (fieldSepString == null) 
442             throw new HL7Exception("Can't encode message: MSH-1 (field separator) is missing");
443         
444         char fieldSep = '|';
445         if (fieldSepString != null && fieldSepString.length() > 0)
446             fieldSep = fieldSepString.charAt(0);
447         
448         String encCharString = Terser.get(msh, 2, 0, 1, 1);
449         
450         if (encCharString == null) 
451             throw new HL7Exception("Can't encode message: MSH-2 (encoding characters) is missing");
452                 
453         if (encCharString.length() != 4)
454             throw new HL7Exception(
455             "Encoding characters '" + encCharString + "' invalid -- must be 4 characters",
456             ErrorCode.DATA_TYPE_ERROR);
457         EncodingCharacters en = new EncodingCharacters(fieldSep, encCharString);
458         
459         //pass down to group encoding method which will operate recursively on children ...
460         return encode((Group) source, en);
461     }
462     
463     /**
464      * Returns given group serialized as a pipe-encoded string - this method is called
465      * by encode(Message source, String encoding).
466      */
467     public static String encode(Group source, EncodingCharacters encodingChars) throws HL7Exception {
468         StringBuffer result = new StringBuffer();
469         
470         String[] names = source.getNames();
471         for (int i = 0; i < names.length; i++) {
472             Structure[] reps = source.getAll(names[i]);
473             for (int rep = 0; rep < reps.length; rep++) {
474                 if (reps[rep] instanceof Group) {
475                     result.append(encode((Group) reps[rep], encodingChars));
476                 }
477                 else {
478                     String segString = encode((Segment) reps[rep], encodingChars);
479                     if (segString.length() >= 4) {
480                         result.append(segString);
481                         result.append('\r');
482                     }
483                 }
484             }
485         }
486         return result.toString();
487     }
488     
489     public static String encode(Segment source, EncodingCharacters encodingChars) {
490         StringBuffer result = new StringBuffer();
491         result.append(source.getName());
492         result.append(encodingChars.getFieldSeparator());
493         
494         //start at field 2 for MSH segment because field 1 is the field delimiter
495         int startAt = 1;
496         if (isDelimDefSegment(source.getName()))
497             startAt = 2;
498         
499         //loop through fields; for every field delimit any repetitions and add field delimiter after ...
500         int numFields = source.numFields();
501         for (int i = startAt; i <= numFields; i++) {
502             try {
503                 Type[] reps = source.getField(i);
504                 for (int j = 0; j < reps.length; j++) {
505                     String fieldText = encode(reps[j], encodingChars);
506                     //if this is MSH-2, then it shouldn't be escaped, so unescape it again
507                     if (isDelimDefSegment(source.getName()) && i == 2)
508                         fieldText = Escape.unescape(fieldText, encodingChars);
509                     result.append(fieldText);
510                     if (j < reps.length - 1)
511                         result.append(encodingChars.getRepetitionSeparator());
512                 }
513             }
514             catch (HL7Exception e) {
515                 log.error("Error while encoding segment: ", e);
516             }
517             result.append(encodingChars.getFieldSeparator());
518         }
519         
520         //strip trailing delimiters ...
521         return stripExtraDelimiters(result.toString(), encodingChars.getFieldSeparator());
522     }
523     
524     /**
525      * Removes leading whitespace from the given string.  This method was created to deal with frequent
526      * problems parsing messages that have been hand-written in windows.  The intuitive way to delimit
527      * segments is to hit <ENTER> at the end of each segment, but this creates both a carriage return
528      * and a line feed, so to the parser, the first character of the next segment is the line feed.
529      */
530     public static String stripLeadingWhitespace(String in) {
531         StringBuffer out = new StringBuffer();
532         char[] chars = in.toCharArray();
533         int c = 0;
534         while (c < chars.length) {
535             if (!Character.isWhitespace(chars[c]))
536                 break;
537             c++;
538         }
539         for (int i = c; i < chars.length; i++) {
540             out.append(chars[i]);
541         }
542         return out.toString();
543     }
544     
545     /**
546      * <p>Returns a minimal amount of data from a message string, including only the
547      * data needed to send a response to the remote system.  This includes the
548      * following fields:
549      * <ul><li>field separator</li>
550      * <li>encoding characters</li>
551      * <li>processing ID</li>
552      * <li>message control ID</li></ul>
553      * This method is intended for use when there is an error parsing a message,
554      * (so the Message object is unavailable) but an error message must be sent
555      * back to the remote system including some of the information in the inbound
556      * message.  This method parses only that required information, hopefully
557      * avoiding the condition that caused the original error.  The other
558      * fields in the returned MSH segment are empty.</p>
559      */
560     public Segment getCriticalResponseData(String message) throws HL7Exception {
561         //try to get MSH segment
562         int locStartMSH = message.indexOf("MSH");
563         if (locStartMSH < 0)
564             throw new HL7Exception(
565             "Couldn't find MSH segment in message: " + message,
566             ErrorCode.SEGMENT_SEQUENCE_ERROR);
567         int locEndMSH = message.indexOf('\r', locStartMSH + 1);
568         if (locEndMSH < 0)
569             locEndMSH = message.length();
570         String mshString = message.substring(locStartMSH, locEndMSH);
571         
572         //find out what the field separator is
573         char fieldSep = mshString.charAt(3);
574         
575         //get field array
576         String[] fields = split(mshString, String.valueOf(fieldSep));
577         
578         Segment msh = null;
579         try {
580             //parse required fields
581             String encChars = fields[1];
582             char compSep = encChars.charAt(0);
583             String messControlID = fields[9];
584             String[] procIDComps = split(fields[10], String.valueOf(compSep));
585             
586             //fill MSH segment
587             String version = Version.lowestAvailableVersion().getVersion(); //default
588             try {
589                 version = this.getVersion(message);
590             }
591             catch (Exception e) { /* use the default */
592             }
593             
594             msh = Parser.makeControlMSH(version, getFactory());
595             Terser.set(msh, 1, 0, 1, 1, String.valueOf(fieldSep));
596             Terser.set(msh, 2, 0, 1, 1, encChars);
597             Terser.set(msh, 10, 0, 1, 1, messControlID);
598             Terser.set(msh, 11, 0, 1, 1, procIDComps[0]);
599             Terser.set(msh, 12, 0, 1, 1, version);
600             
601             }
602         catch (Exception e) {
603             throw new HL7Exception(
604             "Can't parse critical fields from MSH segment ("
605             + e.getClass().getName()
606             + ": "
607             + e.getMessage()
608             + "): "
609             + mshString,
610             ErrorCode.REQUIRED_FIELD_MISSING, e);
611         }
612         
613         return msh;
614     }
615     
616     /**
617      * For response messages, returns the value of MSA-2 (the message ID of the message
618      * sent by the sending system).  This value may be needed prior to main message parsing,
619      * so that (particularly in a multi-threaded scenario) the message can be routed to
620      * the thread that sent the request.  We need this information first so that any
621      * parse exceptions are thrown to the correct thread.
622      * Returns null if MSA-2 can not be found (e.g. if the message is not a
623      * response message).
624      */
625     public String getAckID(String message) {
626         String ackID = null;
627         int startMSA = message.indexOf("\rMSA");
628         if (startMSA >= 0) {
629             int startFieldOne = startMSA + 5;
630             char fieldDelim = message.charAt(startFieldOne - 1);
631             int start = message.indexOf(fieldDelim, startFieldOne) + 1;
632             int end = message.indexOf(fieldDelim, start);
633             int segEnd = message.indexOf(String.valueOf(segDelim), start);
634             if (segEnd > start && segEnd < end)
635                 end = segEnd;
636             
637             //if there is no field delim after MSH-2, need to go to end of message, but not including end seg delim if it exists
638             if (end < 0) {
639                 if (message.charAt(message.length() - 1) == '\r') {
640                     end = message.length() - 1;
641                 }
642                 else {
643                     end = message.length();
644                 }
645             }
646             if (start > 0 && end > start) {
647                 ackID = message.substring(start, end);
648             }
649         }
650         log.debug("ACK ID: {}", ackID);
651         return ackID;
652     }
653     
654     /**
655      * Returns the version ID (MSH-12) from the given message, without fully parsing the message.
656      * The version is needed prior to parsing in order to determine the message class
657      * into which the text of the message should be parsed.
658      * @throws HL7Exception if the version field can not be found.
659      */
660     public String getVersion(String message) throws HL7Exception {
661         int startMSH = message.indexOf("MSH");
662         int endMSH = message.indexOf(OldPipeParser.segDelim, startMSH);
663         if (endMSH < 0)
664             endMSH = message.length();
665         String msh = message.substring(startMSH, endMSH);
666         String fieldSep = null;
667         if (msh.length() > 3) {
668             fieldSep = String.valueOf(msh.charAt(3));
669         }
670         else {
671             throw new HL7Exception("Can't find field separator in MSH: " + msh, ErrorCode.UNSUPPORTED_VERSION_ID);
672         }
673         
674         String[] fields = split(msh, fieldSep);
675         
676         String compSep = null;
677         if (fields.length >= 2 && fields[1] != null && fields[1].length() == 4) {
678             compSep = String.valueOf(fields[1].charAt(0)); //get component separator as 1st encoding char
679         } 
680         else {
681             throw new HL7Exception("Invalid or incomplete encoding characters - MSH-2 is " + fields[1],  
682                     ErrorCode.REQUIRED_FIELD_MISSING);
683         }
684         
685         String version = null;
686         if (fields.length >= 12) {
687         	String[] comp = split(fields[11], compSep);
688         	if (comp.length >= 1) {
689         		version = comp[0];
690         	} else {
691         		throw new HL7Exception("Can't find version ID - MSH.12 is " + fields[11],
692         				ErrorCode.REQUIRED_FIELD_MISSING);
693         	}
694         }
695         else {
696             throw new HL7Exception(
697             		"Can't find version ID - MSH has only " + fields.length + " fields.",
698             		ErrorCode.REQUIRED_FIELD_MISSING);
699         }
700         return version;
701     }
702 
703     /**
704      * {@inheritDoc }
705      */
706     public String doEncode(Segment structure, EncodingCharacters encodingCharacters) throws HL7Exception {
707         return encode(structure, encodingCharacters);
708     }
709 
710     /**
711      * {@inheritDoc }
712      */
713     public String doEncode(Type type, EncodingCharacters encodingCharacters) throws HL7Exception {
714         return encode(type, encodingCharacters);
715     }
716 
717     /**
718      * Throws unsupported operation exception
719      *
720      * @throws Unsupported operation exception
721      */
722     @Override
723 	protected Message doParseForSpecificPackage(String theMessage, String theVersion, String thePackageName) throws HL7Exception, EncodingNotSupportedException {
724         throw new UnsupportedOperationException("Not supported yet.");
725 	}
726     
727     public void parse(Message message, String string) throws HL7Exception {
728         //MessagePointer ptr = new MessagePointer(this, m, getEncodingChars(message));
729         MessageIterator messageIter = new MessageIterator(message, "MSH", true);
730         FilterIterator.Predicate<Structure> segmentsOnly = new FilterIterator.Predicate<Structure>() {
731             public boolean evaluate(Structure obj) {
732                 if (Segment.class.isAssignableFrom(obj.getClass())) {
733                     return true;
734                 } else {
735                     return false;
736                 }
737             }
738         };
739         FilterIterator<Structure> segmentIter = new FilterIterator<Structure>(messageIter, segmentsOnly);
740 
741         String[] segments = split(string, segDelim);
742 
743         char delim = '|';
744         for (int i = 0; i < segments.length; i++) {
745 
746             //get rid of any leading whitespace characters ...
747             if (segments[i] != null && segments[i].length() > 0 && Character.isWhitespace(segments[i].charAt(0)))
748                 segments[i] = stripLeadingWhitespace(segments[i]);
749 
750             //sometimes people put extra segment delimiters at end of msg ...
751             if (segments[i] != null && segments[i].length() >= 3) {
752                 final String name;
753                 if (i == 0) {
754                     name = segments[i].substring(0, 3);
755                     delim = segments[i].charAt(3);
756                 } else {
757                     if (segments[i].indexOf(delim) >= 0 ) {
758                         name = segments[i].substring(0, segments[i].indexOf(delim));
759                       } else {
760                         name = segments[i];
761                       }
762                  }
763 
764                 log.debug("Parsing segment {}", name);
765 
766                 messageIter.setDirection(name);
767                 FilterIterator.Predicate<Structure> byDirection = new FilterIterator.Predicate<Structure>() {
768                     public boolean evaluate(Structure obj) {
769                         Structure s = (Structure) obj;
770                         log.debug("PipeParser iterating message in direction {} at {} ", name, s.getName());
771                         return s.getName().matches(name + "\\d*");
772                     }
773                 };
774                 FilterIterator<Structure> dirIter = new FilterIterator<Structure>(segmentIter, byDirection);
775                 if (dirIter.hasNext()) {
776                     parse((Segment) dirIter.next(), segments[i], getEncodingChars(string));
777                 }
778             }
779         }
780     }
781 
782     
783     /**
784      * A struct for holding a message class string and a boolean indicating whether it 
785      * was defined explicitly.  
786      */
787     private static class MessageStructure {
788         public String messageStructure;
789         public boolean explicitlyDefined;
790         
791         public MessageStructure(String theMessageStructure, boolean isExplicitlyDefined) {
792             messageStructure = theMessageStructure;
793             explicitlyDefined = isExplicitlyDefined;
794         }
795     }
796     
797 }