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