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