001 package org.vmdb.hl7; 002 003 import java.util.*; 004 005 /** 006 * <p><Title:> Abstract Base Class For HL7 Messages and Loops. </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>Provide capabilities common to both Messages and Loops. Each of these 011 * is essentially a collection of segments, loops, or both.</p> 012 * @author Michael K. Martin 013 * @version 1.0 014 */ 015 016 public abstract class HL7SegmentContainer extends HL7Object { 017 protected Vector vSegments; 018 protected StringList slData; 019 protected Stack stState; 020 021 public HL7SegmentContainer() { 022 vSegments = new Vector(); 023 stState = new Stack(); 024 } 025 026 void setData( StringList slData ) { 027 this.slData = slData; 028 } 029 030 Iterator iterator() { 031 if( vSegments == null ) return null; 032 else return vSegments.iterator(); 033 } 034 035 boolean readString( String sData ) { 036 slData = new StringList( sData, '\n' ); 037 stState = new Stack(); 038 if( slData.hasMoreStrings() ) { 039 // Pass this pointer so parser knows to call my processSegment method. 040 RuleParser p = new RuleParser(); 041 try { 042 if( p.parseSegments( getRule(), this ) > 0 ) 043 return true; 044 } 045 catch( Exception e ) { 046 System.err.println( e.toString() ); 047 e.printStackTrace(); 048 } 049 } 050 return false; 051 } 052 053 /** 054 * Allow Rule parser to determine if we've hit the end of the raw HL7 055 * input string (stream).<br> 056 * Package scope so RuleParser can use it but normal application classes 057 * cannot. 058 */ 059 boolean messageComplete() { 060 return( !slData.hasMoreStrings() ); 061 } 062 063 /** 064 * Allow the RuleParser to save state of this container while parsing 065 * a rule so that it can back up from trying optional segment rules.<br> 066 * Package scope so RuleParser can use it but normal application classes 067 * cannot. 068 */ 069 void push() { 070 int iDataIndex = slData.getIndex(); 071 int iSegIndex = vSegments.size(); 072 StateIndex si = new StateIndex(); 073 si.iDataIndex = iDataIndex; 074 si.iSegIndex = iSegIndex; 075 stState.push( si ); 076 } 077 078 /** 079 * Allow the RuleParser to recover state of this container while parsing 080 * a rule so that it can back up from trying optional segment rules.<br> 081 * Package scope so RuleParser can use it but normal application classes 082 * cannot. 083 */ 084 boolean pop() { 085 if( stState.empty() ) 086 return false; 087 StateIndex si = (StateIndex)stState.pop(); 088 // Roll back raw data 089 slData.setIndex( si.iDataIndex ); 090 // Roll back processing 091 vSegments.setSize( si.iSegIndex ); 092 return true; 093 } 094 095 boolean processSegment( String sSeg ) 096 throws org.vmdb.hl7.MalformedSegmentException { 097 String sNextDataRow = null; 098 // Actually read next segment from input and process if name = sSeg 099 // or return false otherwise 100 if( sSeg == null || sSeg.length() == 0 ) 101 return false; 102 if( slData.hasMoreStrings() ) 103 sNextDataRow = slData.nextString(); 104 else 105 return false; 106 if( sSeg.equals( "*" ) || sNextDataRow.startsWith( sSeg ) ) { 107 addSegment( sNextDataRow ); 108 return true; 109 } 110 else { 111 slData.undoNext(); 112 return false; 113 } 114 } 115 116 /** 117 * A "Loop" is a segment container that has its own Vector of segments (or loops) 118 * but which reads from the parent message's raw data stream. So, processing 119 * a loop means constructing a loop object of the correct class (to get the 120 * appropriate rule and other processing) but with a pointer to my data StringList. 121 * @param sRule String containing the name of the loop. Since the official List and Group 122 * names include periods which are not allowed in class names, we convert to addtional 123 * underscores. 124 */ 125 int processLoop( String sRule ) { 126 try { 127 StringBuffer sbRule = new StringBuffer( sRule ); 128 int iLoc = 0; 129 while( (iLoc = sRule.indexOf('.',iLoc+1)) != -1 ) { 130 sbRule.setCharAt(iLoc, '_'); 131 } 132 String sClass = "org.vmdb.hl7." + sbRule.toString() + "Loop"; 133 //System.err.println( "Rule class = " + sClass ); 134 Class c = Class.forName( sClass ); 135 HL7Loop loop = (HL7Loop)c.newInstance(); 136 loop.setData( slData ); 137 // Pass this pointer so parser knows to call my processSegment method. 138 RuleParser p = new RuleParser(); 139 int iNewRows = p.parseSegments( loop.getRule(), loop ); 140 if( iNewRows > 0 ) { 141 vSegments.add( loop ); 142 return iNewRows; 143 } 144 } 145 catch( Exception e ) { 146 System.err.println( e.toString() ); 147 e.printStackTrace(); 148 } 149 return -1; 150 } 151 152 /** 153 * Set separators for the message and all its segments.<br><br> 154 * It is strongly suggested that you leave the default separator set as is. 155 * That said, HL7 does allow different separators so this is how they can 156 * be set. This method is also used within the package to set the default 157 * separators. 158 * @param sSeparators string with separators. 159 */ 160 public void setSeparators( String sSeparators ) { 161 super.setSeparators( sSeparators ); 162 Iterator i = vSegments.iterator(); 163 while( i.hasNext() ) { 164 HL7Segment seg = (HL7Segment)i.next(); 165 seg.setSeparators( sSeparators ); 166 } 167 } 168 169 /** 170 * Find a single segment within this hierarchy by name.<br><br> 171 * vSegments may contain either segments or nested containers so we 172 * "recursively" search. This is a brute force linear search! 173 * Returns the first segment of the name specified. For segments that 174 * repeat, need to use other methods. 175 * @param sSegName String with name of segment wanted as its three letter 176 * abbreviation ("PID","OBR", etc.). 177 * @return A reference to an HL7Segment object or null if not found. 178 */ 179 public HL7Segment findSegment( String sSegName ) { 180 Iterator i = vSegments.iterator(); 181 while( i.hasNext() ) { 182 Object o = i.next(); 183 if( o instanceof HL7Segment ) { 184 HL7Segment sNext = (HL7Segment)o; 185 if( sNext.getName().equalsIgnoreCase( sSegName ) ) 186 return sNext; 187 } 188 else if( o instanceof HL7SegmentContainer ) { 189 HL7SegmentContainer scNext = (HL7SegmentContainer)o; 190 HL7Segment sNext = scNext.findSegment( sSegName ); 191 if( sNext != null ) 192 return sNext; 193 } 194 } 195 return null; 196 } 197 198 /** 199 * Find all segments within this hierarchy by name.<br><br> 200 * vSegments may contain either segments or nested containers so we 201 * "recursively" search. This is a brute force linear search! 202 * Constructs a Vector of all those segments with the specified name 203 * and returns an iterator over those segments.<br> 204 * Note: Implementation is very Java specific because it relies upon 205 * garbage collection to maintain the created Vector until the returned 206 * iterator is destroyed. Other methods will be needed in C++ but the 207 * interface should be similar. 208 * @param sSegName String with name of segment wanted 209 * @return An Iterator over all Segments matching on name. 210 */ 211 public Iterator listsSegments( String sSegName ) { 212 Iterator i = vSegments.iterator(); 213 Vector vMatchedSegs = new Vector(); 214 while( i.hasNext() ) { 215 Object o = i.next(); 216 if( o instanceof HL7Segment ) { 217 HL7Segment sNext = (HL7Segment)o; 218 if( sNext.getName().equalsIgnoreCase( sSegName ) ) 219 vMatchedSegs.add( sNext ); 220 } 221 else if( o instanceof HL7SegmentContainer ) { 222 HL7SegmentContainer scNext = (HL7SegmentContainer)o; 223 HL7Segment sNext = scNext.findSegment( sSegName ); 224 if( sNext != null ) 225 vMatchedSegs.add( sNext ); 226 } 227 } 228 return vMatchedSegs.iterator(); 229 } 230 231 /** 232 * Add a segment already instantiated as an object of type HL7Segment or specific subclass 233 * @param sSegment HL7Segment object 234 * @return true if successful 235 */ 236 boolean addSegment( HL7Segment sSegment ) { 237 if( sSegment == null ) { 238 vSegments = new Vector(); 239 } 240 vSegments.add( sSegment ); 241 return true; 242 } 243 244 /** 245 * Add a segment as a String 246 * @param sSegment String with one row of a properly formed HL7 v2.3 message 247 * @return true if successful 248 */ 249 boolean addSegment( String sSegment ) { 250 HL7Segment sSeg = HL7Segment.fromString( sSegment, this ); 251 return addSegment( sSeg ); 252 } 253 254 /** 255 * Return the message or loop as an HL7String.<br><br> 256 * This should probably be renamed toHL7() but for now the only logical 257 * string representation is the raw HL7. 258 * @return HL7 string of this message or loop. 259 */ 260 public String toString() { 261 Iterator i = vSegments.iterator(); 262 StringBuffer sb = new StringBuffer(); 263 while( i.hasNext() ) { 264 HL7Object o = (HL7Object)i.next(); 265 sb.append( o.toString() ); 266 } 267 return sb.toString(); 268 } 269 270 /** 271 * Output the message or loop as XML.<br><br> 272 * For HL7Message, this will be a full message. For loops it 273 * will be the DOM element for that loop and all its contained objects. 274 * @param iDepth int value for the number of spaces to indent this object. Used just 275 * to make the XML easier to read as unformatted text. 276 * @return foratted XML text. 277 */ 278 public String toXML( int iDepth ) { 279 Iterator i = vSegments.iterator(); 280 StringBuffer sb = new StringBuffer(); 281 for( int x = 0; x < iDepth; x++ ) sb.append( " " ); 282 sb.append( '<' ); 283 sb.append( getName() ); 284 sb.append( ">\n" ); 285 while( i.hasNext() ) { 286 HL7Object o = (HL7Object)i.next(); 287 sb.append( o.toXML( iDepth + 1 ) ); 288 } 289 for( int x = 0; x < iDepth; x++ ) sb.append( " " ); 290 sb.append( "</" ); 291 sb.append( getName() ); 292 sb.append( ">\n" ); 293 return sb.toString(); 294 } 295 } 296 297 // Simple data structure to hold state during push/pop 298 class StateIndex { 299 public int iDataIndex; 300 public int iSegIndex; 301 } 302