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