001/**
002The 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.
004You may obtain a copy of the License at http://www.mozilla.org/MPL/
005Software distributed under the License is distributed on an "AS IS" basis,
006WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
007specific language governing rights and limitations under the License.
008
009The Original Code is "GroupPointer.java".  Description:
010"A GroupPointer is used when parsing traditionally encoded HL7 messages"
011
012The Initial Developer of the Original Code is University Health Network. Copyright (C)
0132001.  All Rights Reserved.
014
015Contributor(s): ______________________________________.
016
017Alternatively, the contents of this file may be used under the terms of the
018GNU General Public License (the  �GPL�), in which case the provisions of the GPL are
019applicable instead of those above.  If you wish to allow use of your version of this
020file only under the terms of the GPL and not to allow others to use your version
021of this file under the MPL, indicate your decision by deleting  the provisions above
022and replace  them with the notice and other provisions required by the GPL License.
023If you do not delete the provisions above, a recipient may use your version of
024this file under either the MPL or the GPL.
025
026*/
027package ca.uhn.hl7v2.parser;
028
029import java.io.BufferedReader;
030import java.io.IOException;
031import java.io.InputStreamReader;
032import java.net.URL;
033import java.net.URLConnection;
034import java.util.ArrayList;
035import java.util.Arrays;
036import java.util.HashMap;
037import java.util.Iterator;
038import java.util.List;
039import java.util.Map;
040import java.util.Stack;
041import java.util.StringTokenizer;
042
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046import ca.uhn.hl7v2.HL7Exception;
047import ca.uhn.hl7v2.model.Message;
048import ca.uhn.hl7v2.model.Primitive;
049import ca.uhn.hl7v2.model.Segment;
050import ca.uhn.hl7v2.model.Type;
051import ca.uhn.hl7v2.util.Terser;
052
053/**
054 * This class has been deprecated and should not be used.
055 * 
056 * PipeParser has been optimized and is now roughly 20% faster than FastParser (see
057 * FastParserTest for a test of this) 
058 *    
059 * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
060 * @version $Revision: 1.3 $ updated on $Date: 2009-10-03 15:25:46 $ by $Author: jamesagnew $
061 * 
062 * @deprecated
063 */
064public class FastParser extends Parser {
065
066    private static final Logger ourLog = LoggerFactory.getLogger(FastParser.class);
067    
068    private static char ourSegmentSeparator = '\r';
069    private Map<Object, StructRef> myEventGuideMap;
070    private PipeParser myPipeParser;
071    
072    /**
073     * @param theEventGuideMap a map with keys in the form "type^event" (like MSH-9 
074     *      components 1 and 2).  Values are corresponding parsing guides for those events.  
075     *      A parsing guide is a group of StructRef that identify which segments to parse, 
076     *      the relationships between them, and where to find them in a message hierarchy.
077     *      The value in the map is the RootRef of the message root.  It must return the 
078     *      StructRef for the MSH segment from getSuccessor("MSH").  References to other 
079     *      segments can be included as needed.   
080     */
081    public FastParser(Map<Object, StructRef> theEventGuideMap) {
082        this(null, theEventGuideMap);
083    }
084
085    /**
086     * @param theFactory custom factory to use for model class lookup 
087     * @param theEventGuideMap a map with keys in the form "type^event" (like MSH-9 
088     *      components 1 and 2).  Values are corresponding parsing guides for those events.  
089     *      A parsing guide is a group of StructRef that identify which segments to parse, 
090     *      the relationships between them, and where to find them in a message hierarchy.
091     *      The value in the map is the RootRef of the message root.  It must return the 
092     *      StructRef for the MSH segment from getSuccessor("MSH").  References to other 
093     *      segments can be included as needed.   
094     */
095    public FastParser(ModelClassFactory theFactory, Map<Object, StructRef> theEventGuideMap) {
096        super(theFactory);
097        myEventGuideMap = theEventGuideMap;
098        myPipeParser = new PipeParser();
099    }
100    
101    /**
102     * Loads a parsing guide map (as required for FastParser instantiation).  The URL should 
103     * point to a file with one or more guides in sections delimited by blank lines.  Within 
104     * a section, the first line must contain an event name of the for "type^event".  Subsequent 
105     * lines define the parsed parts of messages with that event.  Each line begins with either 
106     * a segment name or "{" (indicating group start) or "}" (indicating group end).  Group  
107     * start lines then have whitespace and a Terser path to the group (relative to the closest 
108     * ancestor group listed in the parsin guide).  Segment lines then have whitespace and a 
109     * relative Terser path to the segment, followed by a colon and a comma-delimited list of field 
110     * numbers, which indicates which fields for that segment are to be parsed.  Within Terser
111     * paths, repetition numbers must be replaced with asterisks. An example follows: 
112     * 
113     * ORU^R01
114     * MSH MSH:9,12
115     * { ORU_R01_PIDNTEPV1ORCOBRNTEOBXNTE(*)
116     *     { ORU_R01_PIDNTEPV1
117     *         PID PID:3-5
118     *     }
119     *     { ORU_R01_ORCOBRNTEOBXNTE(*)
120     *         { ORU_R01_OBXNTE(*)
121     *             OBX OBX:2,5
122     *         }
123     *     }
124     * }
125     * 
126     * ADT^A01
127     * MSH MSH:9,12
128     * PID PID:3
129     * PV1 PV1:7-9
130     * 
131     * @param theMapURL an URL to a file of the form desribed above
132     * @return the corresponding Map 
133     */
134    public static Map<Object, StructRef> loadEventGuideMap(URL theMapURL) throws HL7Exception {
135        Map<Object, StructRef> result = new HashMap<Object, StructRef>();
136        
137        try {
138            URLConnection conn = theMapURL.openConnection();
139            BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
140            
141            String eventName = null;
142            StringBuffer spec = new StringBuffer();
143            String line = null;
144            while ((line = reader.readLine()) != null) {
145                if (line.length() == 0) {
146                    finish(eventName, spec, result);
147                    eventName = null;
148                    spec = new StringBuffer();
149                } else {
150                    if (eventName == null) {
151                        eventName = line;
152                    } else {
153                        spec.append(line + "\r");
154                    }
155                }
156            }
157            reader.close();            
158            finish(eventName, spec, result);
159        } catch (IOException e) {
160            throw new HL7Exception(e);
161        }
162        
163        return result;
164    }
165    
166    private static void finish(String theEventName, StringBuffer theSpec, Map<Object, StructRef> theMap) {
167        if (theEventName != null) {
168            RootRef root = parseGuide(theSpec.toString());
169            theMap.put(theEventName, root);
170        }        
171    }
172    
173    private static RootRef parseGuide(String theSpec) {
174        StringTokenizer lines = new StringTokenizer(theSpec, "\r", false);
175        RootRef result = new RootRef();
176        Stack<StructRef> ancestry = new Stack<StructRef>();
177        ancestry.push(result);
178        Map<Object, StructRef> successors = new HashMap<Object, StructRef>();
179        
180        StructRef previous = result;
181        while (lines.hasMoreTokens()) {
182            String line = lines.nextToken();
183            StringTokenizer parts = new StringTokenizer(line, "\t ", false);
184            String segName = parts.nextToken();
185            String path = parts.hasMoreTokens() ? parts.nextToken() : "";
186            parts = new StringTokenizer(path, ":", false);
187            path = parts.hasMoreTokens() ? parts.nextToken() : null;
188            
189            int[] fields = getFieldList(parts.hasMoreTokens() ? parts.nextToken() : ""); 
190            
191            if (segName.equals("}")) {                
192                StructRef parent = (StructRef) ancestry.pop();
193                if (parent.getChildName() != null && parent.getRelativePath().indexOf('*') >= 0) { //repeating group
194                    previous.setSuccessor(parent.getChildName(), parent);                    
195                }
196            } else {
197                boolean isSegment = !(segName.equals("{"));
198                StructRef ref = new StructRef((StructRef) ancestry.peek(), path, isSegment, fields);
199                if (isSegment) {
200                    previous.setSuccessor(segName, ref);
201                    if (path.indexOf('*') >= 0) ref.setSuccessor(segName, ref);
202                    setGroupSuccessors(successors, segName);
203                } else {
204                    successors.put(previous, ref);
205                }
206                if (!isSegment) ancestry.push(ref);
207                previous = ref;
208            }
209        }
210        
211        return result;
212    }
213    
214    private static void setGroupSuccessors(Map<Object, StructRef> theSuccessors, String theSegName) {
215        for (Iterator<Object> it = theSuccessors.keySet().iterator(); it.hasNext(); ) {
216            StructRef from = (StructRef) it.next();
217            StructRef to = (StructRef) theSuccessors.get(from);
218            from.setSuccessor(theSegName, to);
219        }
220        theSuccessors.clear();
221    }
222    
223    private static int[] getFieldList(String theSpec) {
224        StringTokenizer tok = new StringTokenizer(theSpec, ",", false); 
225        List<Integer> fieldList = new ArrayList<Integer>(30);
226        while (tok.hasMoreTokens()) {
227            String token = tok.nextToken();
228            int index = token.indexOf('-');
229            if (index >= 0) { //it's a range
230                int start = Integer.parseInt(token.substring(0, index));
231                int end = Integer.parseInt(token.substring(index+1));
232                for (int i = start; i <= end; i++) {
233                    fieldList.add(new Integer(i));
234                }
235            } else {
236                fieldList.add(Integer.valueOf(token));
237            }
238        }
239        
240        int[] result = new int[fieldList.size()];
241        for (int i = 0; i < result.length; i++) {
242            result[i] = ((Integer) fieldList.get(i)).intValue();
243        }
244        
245        return result;
246    }
247
248    /** 
249     * @see ca.uhn.hl7v2.parser.Parser#getEncoding(java.lang.String)
250     */
251    public String getEncoding(String message) {
252        return myPipeParser.getEncoding(message);
253    }
254
255    /** 
256     * @see ca.uhn.hl7v2.parser.Parser#supportsEncoding(java.lang.String)
257     */
258    public boolean supportsEncoding(String encoding) {
259        return myPipeParser.supportsEncoding(encoding);
260    }
261    
262    /**
263     * @return the preferred encoding of this Parser
264     */
265    public String getDefaultEncoding() {
266        return "VB";
267    }    
268
269    /** 
270     * @see ca.uhn.hl7v2.parser.Parser#doParse(java.lang.String, java.lang.String)
271     */
272    protected Message doParse(String message, String version) throws HL7Exception, EncodingNotSupportedException {
273        Message result = null;
274        
275        char fieldSep = message.charAt(3);
276        EncodingCharacters ec = new EncodingCharacters(fieldSep, message.substring(4, 8));
277        
278        StringTokenizer tok = new StringTokenizer(message.substring(4), 
279                String.valueOf(new char[]{fieldSep, ourSegmentSeparator}), true);
280        
281        String[] mshFields = getMSHFields(tok, fieldSep);
282        Object[] structure = getStructure(mshFields[8], ec.getComponentSeparator());
283        
284        StructRef root = (StructRef) myEventGuideMap.get(structure[0]);
285        if (root == null) {
286            ourLog.debug("FastParser delegating to PipeParser because no metadata available for event {}", 
287                    structure[0]);
288            result = myPipeParser.parse(message);
289        } else {
290//            int csIndex = mshFields[11].indexOf(ec.getComponentSeparator());
291            result = instantiateMessage((String) structure[1], version, ((Boolean) structure[2]).booleanValue());
292                
293            StructRef mshRef = null;
294            synchronized (root) {
295                mshRef = root.getSuccessor("MSH");
296                root.reset();
297            }
298            Segment msh = (Segment) result.get("MSH");
299            for (int i = 0; i < mshRef.getFields().length; i++) {
300                int fieldNum = mshRef.getFields()[i];
301                parse(mshFields[fieldNum-1], msh, fieldNum, ec);
302            }            
303            
304            parse(tok, result, root, ec);
305        }
306        
307        return result;
308    }
309    
310    private String[] getMSHFields(StringTokenizer tok, char fieldSep) {
311        String[] result = new String[21];
312        result[0] = String.valueOf(fieldSep);
313        String token = null;
314        int field = 1;
315        while (tok.hasMoreTokens() && (token = tok.nextToken()).charAt(0) != ourSegmentSeparator) {
316            if (token.charAt(0) == fieldSep) {
317                field++;
318            } else {
319                result[field] = token;
320            }
321        } 
322        return result;
323    }
324    
325    private void parse(StringTokenizer tok, Message message, StructRef root, EncodingCharacters ec) 
326            throws HL7Exception {
327        
328        Terser t = new Terser(message);
329        
330        synchronized (root) {
331            StructRef ref = root.getSuccessor("MSH");            
332            
333            int field = 0;
334            Segment segment = null;
335            int[] fields = new int[0];
336            
337            while (tok.hasMoreTokens()) {
338                String token = tok.nextToken();
339                if (token.charAt(0) == ec.getFieldSeparator()) {
340                    field++;
341                } else if (token.charAt(0) == ourSegmentSeparator) {
342                    field = 0;
343                } else if (field == 0) {
344                    StructRef newref = drill(ref, token);
345                    if (newref == null) {
346                        segment = null;
347                        fields = new int[0];
348                    } else {
349                        ref = newref;
350                        ourLog.debug("Parsing into segment {}", ref.getFullPath());
351                        segment = t.getSegment(ref.getFullPath());
352                        fields = ref.getFields();
353                    }
354                } else if (segment != null && Arrays.binarySearch(fields, field) >= 0) {
355                    parse(token, segment, field, ec);
356                }
357            }
358            root.reset();
359        }        
360    }
361    
362    //drill through groups to a segment 
363    private StructRef drill(StructRef ref, String name) {
364        ref = ref.getSuccessor(name);
365        while (ref != null && !ref.isSegment()) {
366            ref = ref.getSuccessor(name);
367        }
368        return ref;
369    }
370    
371    private void parse(String field, Segment segment, int num, EncodingCharacters ec) throws HL7Exception {
372        if (field != null) {
373            int rep = 0;
374            int component = 1;
375            int subcomponent = 1;
376            Type type = segment.getField(num, rep);
377            
378            String delim = String.valueOf(new char[]{ec.getRepetitionSeparator(), 
379                    ec.getComponentSeparator(), ec.getSubcomponentSeparator()});
380            for (StringTokenizer tok = new StringTokenizer(field, delim, true); tok.hasMoreTokens(); ) {
381                String token = tok.nextToken();
382                char c = token.charAt(0);
383                if (c == ec.getRepetitionSeparator()) {
384                    rep++;
385                    component = 1;
386                    subcomponent = 1;
387                    type = segment.getField(num, rep);
388                } else if (c == ec.getComponentSeparator()) {
389                    component++;
390                    subcomponent = 1;
391                } else if (c == ec.getSubcomponentSeparator()) {
392                    subcomponent++;
393                } else {
394                    Primitive p = Terser.getPrimitive(type, component, subcomponent);
395                    p.setValue(token);
396                }
397            }               
398        }
399    }
400    
401    /**
402     * @returns the message structure from MSH-9-3
403     */
404    private Object[] getStructure(String msh9, char compSep) throws HL7Exception {
405        String structure = null;
406        String event = null;
407        
408        String[] components = new String[3];
409        StringTokenizer tok = new StringTokenizer(msh9, String.valueOf(compSep), true);
410        for (int i = 0; tok.hasMoreTokens() && i < components.length; ) {
411            String token = tok.nextToken();
412            if (token.charAt(0) == compSep) {
413                i++;
414            } else {
415                components[i] = token;                
416            }
417        }
418
419        boolean explicitlyDefined = (components[2] == null) ? false : true;
420
421        if (explicitlyDefined) {
422            structure = components[2];
423        } else if (components[0] != null && components[0].equals("ACK")) {
424            structure = "ACK";
425        } else if (components[0] != null && components[1] != null) {
426            structure = components[0] + "_" + components[1];
427        } else {
428            throw new HL7Exception("Can't determine message structure from MSH-9: " + msh9, 
429                    HL7Exception.UNSUPPORTED_MESSAGE_TYPE);
430        }
431        
432        if (components[1] == null) {
433            event = components[0];
434        } else {
435            event = components[0] + "^" + components[1];
436        }
437        
438        return new Object[] {event, structure, Boolean.valueOf(explicitlyDefined)};
439    }
440    
441
442    /** 
443     * @see ca.uhn.hl7v2.parser.Parser#encode(ca.uhn.hl7v2.model.Message, java.lang.String)
444     */
445    protected String doEncode(Message source, String encoding) throws HL7Exception,
446            EncodingNotSupportedException {
447        return myPipeParser.doEncode(source, encoding);
448    }
449
450    /** 
451     * @see ca.uhn.hl7v2.parser.Parser#encode(ca.uhn.hl7v2.model.Message)
452     */
453    protected String doEncode(Message source) throws HL7Exception {
454        return myPipeParser.doEncode(source);
455    }
456
457    /** 
458     * @see ca.uhn.hl7v2.parser.Parser#getCriticalResponseData(java.lang.String)
459     */
460    public Segment getCriticalResponseData(String message) throws HL7Exception {
461        return myPipeParser.getCriticalResponseData(message);
462    }
463
464    /** 
465     * @see ca.uhn.hl7v2.parser.Parser#getAckID(java.lang.String)
466     */
467    public String getAckID(String message) {
468        return myPipeParser.getAckID(message);
469    }
470
471    /** 
472     * @see ca.uhn.hl7v2.parser.Parser#getVersion(java.lang.String)
473     */
474    public String getVersion(String message) throws HL7Exception {
475        return myPipeParser.getVersion(message);
476    }
477
478    /**
479     * Not supported, throws UnsupportedOperationException
480     *
481     * @throws UnsupportedOperationException
482     */
483    @Override
484    public String doEncode(Segment structure, EncodingCharacters encodingCharacters) throws HL7Exception {
485        throw new UnsupportedOperationException("Not supported yet.");
486    }
487
488    /**
489     * Not supported, throws UnsupportedOperationException
490     *
491     * @throws UnsupportedOperationException
492     */
493    @Override
494    public String doEncode(Type type, EncodingCharacters encodingCharacters) throws HL7Exception {
495        throw new UnsupportedOperationException("Not supported yet.");
496    }
497
498    /**
499     * Not supported, throws UnsupportedOperationException
500     *
501     * @throws UnsupportedOperationException
502     */
503    @Override
504    public void parse(Type type, String string, EncodingCharacters encodingCharacters) throws HL7Exception {
505        throw new UnsupportedOperationException("Not supported yet.");
506    }
507
508    /**
509     * Not supported, throws UnsupportedOperationException
510     *
511     * @throws UnsupportedOperationException
512     */
513    @Override
514    public void parse(Segment segment, String string, EncodingCharacters encodingCharacters) throws HL7Exception {
515        throw new UnsupportedOperationException("Not supported yet.");
516    }
517
518    
519    /**
520     * Not supported, throws UnsupportedOperationException
521     *
522     * @throws UnsupportedOperationException
523     */
524    @Override
525    public void parse(Message message, String string) throws HL7Exception {
526        throw new UnsupportedOperationException("Not supported yet.");
527    }
528    
529    /**
530     * Throws unsupported operation exception
531     *
532     * @throws Unsupported operation exception
533     */
534    @Override
535        protected Message doParseForSpecificPackage(String theMessage, String theVersion, String thePackageName) throws HL7Exception, EncodingNotSupportedException {
536        throw new UnsupportedOperationException("Not supported yet.");
537        }
538    
539    /**
540     * A pointer to a distinct segment or group position in a message.  
541     *  
542     * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
543     * @version $Revision: 1.3 $ updated on $Date: 2009-10-03 15:25:46 $ by $Author: jamesagnew $
544     */
545    public static class StructRef {
546
547        private StructRef myParent;
548        private String myRelativePath;
549        private Map<Object, StructRef> mySuccessors;
550        private int myRep;
551        private boolean mySegmentFlag;
552        //private boolean myResettableFlag;
553        private int[] myFields;
554        private List<StructRef> myChildren;
555        
556        /**
557         * @param theParent a StructRef for the parent Group of the referenced Structure
558         * @param theRelativePath the relative (from the parent) Terser path to the referenced 
559         *      structure.  If the structure repeats, the rep number should be replaced with "*"
560         *      (it will be incremented as needed). 
561         * @param isSegment true iff the referenced Structure is a Segment (rather than a Group)
562         * @param theFields a list of fields to be parsed for this segment (null or empty for groups)
563         */
564        public StructRef(StructRef theParent, String theRelativePath, boolean isSegment, int[] theFields) {
565            myParent = theParent;
566            myChildren = new ArrayList<StructRef>();
567            if (myParent != null) myParent.addChild(this);
568            
569            myRelativePath = theRelativePath;
570            if (!myRelativePath.startsWith("/")) {
571                myRelativePath = "/" + myRelativePath;
572            }
573            mySegmentFlag = isSegment;
574            mySuccessors = new HashMap<Object, StructRef>();
575            myRep = -1;
576            if (mySegmentFlag) {
577                myFields = theFields;
578                Arrays.sort(myFields);                
579            } else {
580                myFields = new int[0];
581            }
582            //myResettableFlag = (myParent == null) ? true : false;
583        }
584        
585        /**
586         * Indicates an immediately subsequent structure in parsing order.  A Structure in a list 
587         * should point to the next Structure in the list.  A Structure that repeats should point to 
588         * itself.  A Structure at the end of a repeating Group should point to the Group. 
589         * A Group should point to its first child.  
590         * 
591         * @param theName name of the next Segment in this direction (ie if the next structure is a group, 
592         *      not that one)
593         * @param theSuccessor the immediately next StructRef in that direction
594         */
595        public void setSuccessor(String theName, StructRef theSuccessor) {
596            mySuccessors.put(theName, theSuccessor);
597        }
598        
599        /**
600         * @return full Terser path, including parent and repetition information.  
601         */
602        public String getFullPath() {
603            return myParent.getFullPath() + myRelativePath.replaceAll("\\*", String.valueOf(myRep));
604        }
605        
606        /**
607         * @return relative Terser path as defined in constructor
608         */
609        public String getRelativePath() {
610            return myRelativePath;
611        }
612        
613        /**
614         * @param theName name of a successor in parse order, as set in setSuccessor()
615         * @return the StructRef under that name 
616         */
617        public StructRef getSuccessor(String theName) {
618            StructRef ref = (StructRef) mySuccessors.get(theName);
619            if (ref != null) {
620                ref.next();
621            } 
622            return ref;
623        }
624        
625        /**
626         * @return name of first successor, if available and if this is not a segment reference, 
627         *      otherwise null 
628         */
629        public String getChildName() {
630            String result = null;
631            if (!mySegmentFlag && !mySuccessors.isEmpty()) {
632                result = (String) mySuccessors.keySet().iterator().next();                
633            }
634            return result;
635        }
636        
637        /**
638         * @return true iff referenced Structure is a Segment 
639         */
640        public boolean isSegment() {
641            return mySegmentFlag;
642        }
643        
644        /**
645         * Increments the repetition number of the underlying Structure, which is used in getFullPath() 
646         */
647        private void next() {
648            myRep++;
649            resetChildren();
650        }
651        
652        private void addChild(StructRef theChild) {
653            if (!isSegment()) {
654                myChildren.add(theChild);
655            }
656        }
657        
658        /**
659         * Resets the StructRef to its starting state, before its first iteration, and resets 
660         * its children as well.  
661         */
662        public void reset() {
663            myRep = -1;
664            resetChildren();
665        }
666        
667        private void resetChildren() {
668            for (int i = 0; i < myChildren.size(); i++) {
669                StructRef child = (StructRef) myChildren.get(i);
670                child.reset();
671            }            
672        }
673        
674        /**
675         * @return an ordered list of fields to be parsed for this segment (empty if not a segment)
676         */
677        public int[] getFields() {
678            return myFields;
679        }
680        
681    }
682    
683    /**
684     * A convenience StructRef that points to a message root.  
685     * 
686     * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
687     * @version $Revision: 1.3 $ updated on $Date: 2009-10-03 15:25:46 $ by $Author: jamesagnew $
688     */
689    public static class RootRef extends StructRef {
690        public RootRef() {
691            super(null, "", false, null);
692        }
693        
694        public String getFullPath() { 
695            return "";
696        }
697    }
698
699}