001    package org.vmdb.hl7;
002    
003    import java.util.*;
004    
005    /**
006     * <p><Title:> ORDER_OBSERVATION Loop. </p>
007     * <p>Description: HL7 Network Connectivity For VMDB. </p>
008     * <p>Copyright: Copyright (c) 2002-2003. </p>
009     * <p>Company: Veterinary Medical Database (VMDB). </p>
010     * <p>The XML representation of HL7 2.x introduces the concepts of groups and lists
011     * that, while present in the construction rules of delimited HL7, are not explicitly
012     * named or labelled in the messages themselves.  This class follows the XML
013     * version with explicitly constructed groups and lists
014     * which we've combined under the general term (borrowed from X12) of "loop."</p>
015     * <p>Most of the repetion and grouping facilitated by this "loop" logic is not
016     * used in the VMDB version of the ORU message but is maintained to retain full
017     * standard compliance and to allow extending this model without fear of losing
018     * interoperability.</p>
019     * <p>Loop and Segment Nesting:  The outline below shows how the loops and minimum
020     * required segments nest. {OBX} indicates that any number of OBX segments may
021     * appear at this location.</p>
022    <pre>
023    MSH
024    PATIENT_RESULT
025        PATIENT
026          PID
027          PATIENT_VISIT
028            PV1
029        <b>ORDER_OBSERVATION</b>
030         [ORC]
031          OBR
032            OBSERVATION
033              OBX
034    </pre>
035     * <p>Besides maintaining the structure of the XML representation, this loop
036     * handles the task of keeping track of set id's and sub id's for the OBX segments
037     * (OBSERVATION Loops) contained within it.</p>
038     * @author Michael K. Martin
039     * @version 1.0
040     */
041    
042    public class ORDER_OBSERVATIONLoop extends HL7Loop {
043       private String sNm = "ORDER_OBSERVATION";
044       private String sRl = "[ORC]OBR{[NTE]}[CTD]{OBSERVATION}[{FT1}]{[CTI]}";
045    
046       // Sequential id for obx's
047       private int iSetId;
048       // Sequential id for top obx followed by others that modify it
049       private int iTopSubId;
050    
051       public ORDER_OBSERVATIONLoop() {
052          super();
053          setName( sNm );
054          setRule( sRl );
055          iSetId = 1;
056          iTopSubId = 1;
057       }
058    
059       /**
060        * Get the OBSERVATIONLoop loop that should be located in this loop.  If the loop
061        * does not exist, take the necessary steps to create it in the appropriate
062        * location.
063        * @return OBSERVATIONLoop object from the correct location.
064        */
065       public OBSERVATIONLoop getOBSERVATION() {
066          OBSERVATIONLoop loop = null;
067          if( vSegments == null ) vSegments = new Vector();
068          for( Iterator i = vSegments.iterator(); i.hasNext(); ) {
069             HL7Object o = (HL7Object)i.next();
070             if( o.getName().equals( "OBSERVATION" ) ) {
071                loop = (OBSERVATIONLoop)o;
072                break;
073             }
074          }
075          if( loop == null ) {
076             loop = new OBSERVATIONLoop();
077             int iLoc = vSegments.size();
078             // This little hack is to try to be sure we insert before
079             // either of these obscure segments should they exist.
080             if( findSegment("FT1") != null ) iLoc--;
081             if( findSegment("CTI") != null ) iLoc--;
082             vSegments.add(iLoc,loop);
083          }
084          return loop;
085       }
086    
087       /**
088        * Get the OBR segment that should be located in this loop.  If
089        * it does not exist, take the necessary steps to create it.<br><br>
090        * Note: The semantics here only work correctly for segments that exist singly
091        * in a message.  We break with generic HL7 here in that only under VMDB are
092        * we restricted to a single patient per message.  If we drop this limitation
093        * we will need to create more complex (addOBR) semantics.
094        * @return OBRSegment object located at the appropriate place in the loop.
095        */
096       public OBRSegment getOBR() {
097          OBRSegment obr = (OBRSegment)findSegment( "OBR" );
098          if( obr == null ) {
099             obr = new OBRSegment();
100             obr.initialize();
101             if( vSegments == null ) vSegments = new Vector();
102             // locate after ORC if it exists.
103             int iLoc = (findSegment("ORC") == null) ? 0 : 1;
104             vSegments.add(iLoc,obr);
105          }
106          return obr;
107       }
108    
109       /**
110        * Get the ORC segment that should be located in this loop.  If
111        * it does not exist, take the necessary steps to create it.<br><br>
112        * Note: The semantics here only work correctly for segments that exist singly
113        * in a message.  We break with generic HL7 here in that only under VMDB are
114        * we restricted to a single patient per message.  If we drop this limitation
115        * we will need to create more complex (addOBR) semantics.
116        * @return OBRSegment object located at the appropriate place in the loop.
117        */
118       public ORCSegment getORC() {
119          ORCSegment orc = (ORCSegment)findSegment( "ORC" );
120          if( orc == null ) {
121             orc = new ORCSegment();
122             orc.initialize();
123             if( vSegments == null ) vSegments = new Vector();
124             vSegments.add(0,orc);
125          }
126          return orc;
127       }
128    
129       /**
130        * Add an OBX segment that should be located in this loop.  If the loop
131        * does not exist, take the necessary steps to create it.<br>
132        * Note:  Adding an OBX really means adding a whole OBSERVATION but
133        * since we usually omit the NTE segment, it is much more intuitive to add
134        * directly to the ORDER_OBSERVATION.  If we add support for the NTE, it
135        * will need a method for locating the OBX it goes with.
136        * @return OBXSegment object newly created.
137        */
138       public OBXSegment addOBX() {
139          OBSERVATIONLoop loop = new OBSERVATIONLoop();
140          OBXSegment obx = loop.getOBX();
141          obx.setSetId( Integer.toString( iSetId++ ) );
142          obx.setSubId( Integer.toString( iTopSubId++ ) );
143          if( vSegments == null ) vSegments = new Vector();
144          int iLoc = vSegments.size();
145          // This little hack is to try to be sure we insert before
146          // either of these obscure segments should they exist.
147          if( findSegment("FT1") != null ) iLoc--;
148          if( findSegment("CTI") != null ) iLoc--;
149          vSegments.add(iLoc,loop);
150          return obx;
151       }
152    
153       /**
154        * Add an OBX segment that should be located in this loop.  If the loop
155        * does not exist, take the necessary steps to create it.  This should not
156        * normally be called by high-level code.  Normally this is handled by
157        * modifyObservation().
158        * @param sSubId String with explicitly defined subId
159        * @return OBXSegment object newly created.
160        */
161       OBXSegment addOBX( String sSubId ) {
162          OBSERVATIONLoop loop = new OBSERVATIONLoop();
163          OBXSegment obx = loop.getOBX();
164          obx.setSetId( Integer.toString( iSetId++ ) );
165          obx.setSubId( sSubId );
166          if( vSegments == null ) vSegments = new Vector();
167          int iLoc = vSegments.size();
168          // This little hack is to try to be sure we insert before
169          // either of these obscure segments should they exist.
170          if( findSegment("FT1") != null ) iLoc--;
171          if( findSegment("CTI") != null ) iLoc--;
172          vSegments.add(iLoc,loop);
173          return obx;
174       }
175    
176       /**
177        * Find all OBX segments with a given Observation Identifier as defined
178        * by a predefined CEElement object.<br>
179        * Note: This implementation <strong>only</strong> works because Java is
180        * garbage collected.  A C++ port is going to have to find a better way
181        * to hold the collection of matched segment or perhaps create a specific
182        * derived Iterator class to walk the collection as the iterator is incremented.
183        * @param ceObsId CEElement with specific observation identifier.  Almost
184        * always using a predefined constant from the Loinc class.
185        * @return Iterator over collection of OBX segments that match on
186        * observation identifier value.
187        */
188       public Iterator findOBXSegments( CEElement ceObsId ) {
189          // vMatches will survive until the last reference to the returned
190          // iterator is out of scope.
191          Vector vMatches = new Vector(); // to hold references
192          if( vSegments != null ) {
193             for( Iterator i = vSegments.iterator(); i.hasNext(); ) {
194                HL7Object o = (HL7Object)i.next();
195                if( o.getName().equals( "OBSERVATION" ) ) {
196                   OBSERVATIONLoop loop = (OBSERVATIONLoop)o;
197                   OBXSegment obx = loop.getOBX();
198                   if( obx.getObservationIdentifier().toString().equals( ceObsId.toString() ) ) {
199                      vMatches.add( obx );
200                   } // End with correct obsId value
201                }// End is an OBX segment
202             }// End for each segment
203          }// End has segments
204          return vMatches.iterator();
205       }
206    
207       /**
208        * Create the sub id to be assigned the next modifier of the OBXSegment
209        * provided.<br><br>
210        * Based on the input OBX's subId, look for all modifiers and assign the next higher
211        * last digit to create the correct subId to assign the next obx that modifies
212        * this one.
213        * @param obxIn An OBXSegment who's subId field will be used to find all
214        * other obx's in this loop that modify it.
215        * @return String to be used as subId for the next modifier added.
216        */
217       public String nextModifierSubId( OBXSegment obxIn ) {
218          // vMatches will survive until the last reference to the returned
219          // iterator is out of scope.
220          String sSubIdIn = obxIn.getSubId() + ".";
221          int iLen = sSubIdIn.length();
222          int iModNo = 0;
223          if( vSegments != null ) {
224             for( Iterator i = vSegments.iterator(); i.hasNext(); ) {
225                HL7Object o = (HL7Object)i.next();
226                if( o.getName().equals( "OBSERVATION" ) ) {
227                   OBSERVATIONLoop loop = (OBSERVATIONLoop)o;
228                   OBXSegment obx = loop.getOBX();
229                   String sSubId = obx.getSubId();
230                   // If the found obx has a subId, that starts with the current subId
231                   // followed by a period and contains no more periods (ie modifies a modifier)
232                   if( sSubId != null &&
233                       sSubIdIn.regionMatches( 0, sSubId, 0, iLen) &&
234                       sSubId.indexOf( '.',iLen ) == -1 ) {
235                      String sModNo = sSubId.substring( iLen );
236                      try {
237                         int iNewModNo = Integer.parseInt( sModNo );
238                         if( iNewModNo > iModNo )
239                            iModNo = iNewModNo;
240                      } catch( NumberFormatException ne ) {
241                         System.err.println( "Cannot parse " + sModNo + " as number" );
242                         ne.printStackTrace();
243                      }
244    
245                   } // End with correct obsId value
246                }// End is an OBX segment
247             }// End for each segment
248          }// End has segments
249          return sSubIdIn + Integer.toString(iModNo+1);
250       }
251    
252       /**
253        * Find all OBX segments whos subId indicates they modify this OBX.<br><br>
254        * Note: This implementation <strong>only</strong> works because Java is
255        * garbage collected.  A C++ port is going to have to find a better way
256        * to hold the collection of matched segment or perhaps create a specific
257        * derived Iterator class to walk the collection as the iterator is incremented.
258        * @param obxIn An OBXSegment whos subId field will be used to find all
259        * other obx's in this loop that modify it.
260        * @return Iterator over collection of OBX segments that modify the supplied
261        * obx.
262        */
263       public Iterator findOBXModifiers( OBXSegment obxIn ) {
264          // vMatches will survive until the last reference to the returned
265          // iterator is out of scope.
266          String sSubIdIn = obxIn.getSubId() + ".";
267          int iLen = sSubIdIn.length();
268          Vector vMatches = new Vector(); // to hold references
269          if( vSegments != null ) {
270             for( Iterator i = vSegments.iterator(); i.hasNext(); ) {
271                HL7Object o = (HL7Object)i.next();
272                if( o.getName().equals( "OBSERVATION" ) ) {
273                   OBSERVATIONLoop loop = (OBSERVATIONLoop)o;
274                   OBXSegment obx = loop.getOBX();
275                   String sSubId = obx.getSubId();
276                   // If the found obx has a subId, that starts with the current subId
277                   // followed by a period and contains no more periods (ie modifies a modifier)
278                   if( sSubId != null &&
279                       sSubIdIn.regionMatches( 0, sSubId, 0, iLen ) &&
280                       sSubId.indexOf( '.',iLen ) == -1 ) {
281                      vMatches.add( obx );
282                   } // End with correct obsId value
283                }// End is an OBX segment
284             }// End for each segment
285          }// End has segments
286          return vMatches.iterator();
287       }
288    
289    }// End class ORDER_OBSERVATIONLoop