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