View Javadoc
1   package ca.uhn.hl7v2.parser;
2   
3   import java.util.ArrayList;
4   import java.util.Arrays;
5   import java.util.List;
6   import java.util.NoSuchElementException;
7   
8   import org.slf4j.Logger;
9   import org.slf4j.LoggerFactory;
10  
11  import ca.uhn.hl7v2.HL7Exception;
12  import ca.uhn.hl7v2.model.Group;
13  import ca.uhn.hl7v2.model.Message;
14  import ca.uhn.hl7v2.model.Structure;
15  
16  /**
17   * Iterates over all defined nodes (ie segments, groups) in a message,
18   * regardless of whether they have been instantiated previously. This is a
19   * tricky process, because the number of nodes is infinite, due to infinitely
20   * repeating segments and groups. See <code>next()</code> for details on how
21   * this is handled.
22   * 
23   * This implementation assumes that the first segment in each group is present
24   * (as per HL7 rules). Specifically, when looking for a segment location, an
25   * empty group that has a spot for the segment will be overlooked if there is
26   * anything else before that spot. This may result in surprising (but sensible)
27   * behaviour if a message is missing the first segment in a group.
28   * 
29   * @author Bryan Tripp
30   */
31  public class MessageIterator implements java.util.Iterator<Structure> {
32  
33      private final Message myMessage;
34      private String myDirection;
35      private boolean myNextIsSet;
36      private final boolean myHandleUnexpectedSegments;
37      private List<Position> myCurrentDefinitionPath = new ArrayList<>();
38  
39      private static final Logger log = LoggerFactory.getLogger(MessageIterator.class);
40  
41      /*
42       * may add configurability later ... private boolean findUpToFirstRequired;
43       * private boolean findFirstDescendentsOnly;
44       * 
45       * public static final String WHOLE_GROUP; public static final String
46       * FIRST_DESCENDENTS_ONLY; public static final String UP_TO_FIRST_REQUIRED;
47       */
48  
49      /** Creates a new instance of MessageIterator */
50      public MessageIterator(Message start, IStructureDefinition startDefinition, String direction, boolean handleUnexpectedSegments) {
51          this.myMessage = start;
52          this.myDirection = direction;
53          this.myHandleUnexpectedSegments = handleUnexpectedSegments;
54          this.myCurrentDefinitionPath.add(new Position(startDefinition, -1));
55      }
56  
57      private Position getCurrentPosition() {
58          return getTail(myCurrentDefinitionPath);
59      }
60  
61      private Position getTail(List<Position> theDefinitionPath) {
62          return theDefinitionPath.get(theDefinitionPath.size() - 1);
63      }
64  
65      private List<Position> popUntilMatchFound(List<Position> theDefinitionPath) {
66          theDefinitionPath = new ArrayList<>(theDefinitionPath.subList(0, theDefinitionPath.size() - 1));
67  
68          if (theDefinitionPath.size() == 0) {
69              return null;
70          }
71  
72          Position newCurrentPosition = getTail(theDefinitionPath);
73          IStructureDefinition newCurrentStructureDefinition = newCurrentPosition.getStructureDefinition();
74  
75          if (newCurrentStructureDefinition.getAllPossibleFirstChildren().contains(myDirection)) {
76              return theDefinitionPath;
77          }
78  
79          if (newCurrentStructureDefinition.isFinalChildOfParent()) {
80              if (theDefinitionPath.size() > 1) {
81                  return popUntilMatchFound(theDefinitionPath); // recurse
82              } else {
83              	log.debug("Popped to root of message and did not find a match for {}", myDirection);
84                  return null;
85              }
86          }
87  
88          return theDefinitionPath;
89      }
90  
91      /**
92       * Returns true if another object exists in the iteration sequence.
93       */
94      @Override
95      public boolean hasNext() {
96  
97          log.trace("hasNext() for direction {}", myDirection);
98          if (myDirection == null) {
99              throw new IllegalStateException("Direction not set");
100         }
101 
102         while (!myNextIsSet) {
103 
104             Position currentPosition = getCurrentPosition();
105 
106             log.trace("hasNext() current position: {}", currentPosition);
107 
108             IStructureDefinition structureDefinition = currentPosition.getStructureDefinition();
109             
110             if (myMessage.getParser().getParserConfiguration().isNonGreedyMode()) {
111             	IStructureDefinition nonGreedyPosition = couldBeNotGreedy();
112             	if (nonGreedyPosition != null) {
113             		log.info("Found non greedy parsing choice, moving to {}", nonGreedyPosition.getName());
114             		while (getCurrentPosition().getStructureDefinition() != nonGreedyPosition) {
115             			myCurrentDefinitionPath.remove(myCurrentDefinitionPath.size() - 1);
116             		}
117             	}
118             }
119             
120             if (structureDefinition.isSegment() && structureDefinition.getName().startsWith(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) {
121                 myNextIsSet = true;
122                 currentPosition.incrementRep();
123             } else if (structureDefinition.isSegment() && structureDefinition.getNextLeaf() == null
124                     && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) {
125                 if (!myHandleUnexpectedSegments) {
126                     return false;
127                 }
128                 addNonStandardSegmentAtCurrentPosition();
129             } else if (structureDefinition.hasChildren() && structureDefinition.getAllPossibleFirstChildren().contains(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) {
130                 currentPosition.incrementRep();
131                 myCurrentDefinitionPath.add(new Position(structureDefinition.getFirstChild(), -1));
132             } else if (!structureDefinition.hasChildren() && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) {
133                 if (!myHandleUnexpectedSegments) {
134                     return false;
135                 }
136                 addNonStandardSegmentAtCurrentPosition();
137                 // } else if (structureDefinition.isMessage()) {
138                 // if (!handleUnexpectedSegments) {
139                 // return false;
140                 // }
141                 // addNonStandardSegmentAtCurrentPosition();
142             } else if (structureDefinition.isFinalChildOfParent()) {
143                 List<Position> newDefinitionPath = popUntilMatchFound(myCurrentDefinitionPath);
144                 if (newDefinitionPath != null) {
145                     // found match
146                     myCurrentDefinitionPath = newDefinitionPath;
147                 } else {
148                     if (!myHandleUnexpectedSegments) {
149                         return false;
150                     }
151                     addNonStandardSegmentAtCurrentPosition();
152                 }
153             } else {
154                 currentPosition.setStructureDefinition(structureDefinition.getNextSibling());
155                 currentPosition.resetRepNumber();
156             }
157 
158         }
159 
160         return true;
161     }
162 
163     /**
164      * @see ParserConfiguration#setNonGreedyMode(boolean)
165      */
166     private IStructureDefinition couldBeNotGreedy() {
167     	for (int i = myCurrentDefinitionPath.size() - 1; i >= 1; i--) {
168     		Position position = myCurrentDefinitionPath.get(i);
169 	    	IStructureDefinition curPos = position.getStructureDefinition();
170 	    	if (curPos.getPosition() > 0) {
171 	    		IStructureDefinition parent = curPos.getParent();
172 				if (parent.isRepeating() && parent.getAllPossibleFirstChildren().contains(myDirection)) {
173 	    			return parent;
174 	    		}
175 	    	}
176 	    	
177     	}
178     	
179 		return null;
180 	}
181 
182 	private void addNonStandardSegmentAtCurrentPosition() throws Error {
183     	log.debug("Creating non standard segment {} on group: {}", 
184     			myDirection, getCurrentPosition().getStructureDefinition().getParent().getName());
185         
186     	List<Position> parentDefinitionPath;
187         Group parentStructure;
188         
189         switch (myMessage.getParser().getParserConfiguration().getUnexpectedSegmentBehaviour()) {
190         case ADD_INLINE:
191         default:
192         	parentDefinitionPath = new ArrayList<>(myCurrentDefinitionPath.subList(0, myCurrentDefinitionPath.size() - 1));
193         	parentStructure = (Group) navigateToStructure(parentDefinitionPath);
194         	break;
195         case DROP_TO_ROOT:
196         	parentDefinitionPath = new ArrayList<>(myCurrentDefinitionPath.subList(0, 1));
197         	parentStructure = myMessage;
198         	myCurrentDefinitionPath = myCurrentDefinitionPath.subList(0, 2);
199         	break;
200         case THROW_HL7_EXCEPTION:
201         	throw new Error(new HL7Exception("Found unknown segment: " + myDirection));
202         }
203         
204         
205         // Current position within parent
206         Position currentPosition = getCurrentPosition();
207 		String nameAsItAppearsInParent = currentPosition.getStructureDefinition().getNameAsItAppearsInParent();
208 
209 		int index = Arrays.asList(parentStructure.getNames()).indexOf(nameAsItAppearsInParent) + 1;
210 		
211         String newSegmentName;
212 		
213 		// Check if the structure already has a non-standard segment in the appropriate
214 		// position
215 		String[] currentNames = parentStructure.getNames();
216 		if (index < currentNames.length && currentNames[index].startsWith(myDirection)) {
217 			newSegmentName = currentNames[index];
218 		} else { 
219 	        try {
220 	            newSegmentName = parentStructure.addNonstandardSegment(myDirection, index);
221 	        } catch (HL7Exception e) {
222 	            throw new Error("Unable to add nonstandard segment " + myDirection + ": ", e);
223 	        }
224 	    }
225 		
226         IStructureDefinition previousSibling = getCurrentPosition().getStructureDefinition();
227         IStructureDefinition parentStructureDefinition = parentDefinitionPath.get(parentDefinitionPath.size() - 1).getStructureDefinition();
228         NonStandardStructureDefinitiontandardStructureDefinition">NonStandardStructureDefinition nextDefinition = new NonStandardStructureDefinition(parentStructureDefinition, previousSibling, newSegmentName, index);
229         myCurrentDefinitionPath = parentDefinitionPath;
230         myCurrentDefinitionPath.add(new Position(nextDefinition, 0));
231 
232         myNextIsSet = true;
233     }
234 
235     /**
236      * <p>
237      * Returns the next node in the message. Sometimes the next node is
238      * ambiguous. For example at the end of a repeating group, the next node may
239      * be the first segment in the next repetition of the group, or the next
240      * sibling, or an undeclared segment locally added to the group's end. Cases
241      * like this are disambiguated using getDirection(), which returns the name
242      * of the structure that we are "iterating towards". Usually we are
243      * "iterating towards" a segment of a certain name because we have a segment
244      * string that we would like to parse into that node. Here are the rules:
245      * </p>
246      * <ol>
247      * <li>If at a group, next means first child.</li>
248      * <li>If at a non-repeating segment, next means next "position"</li>
249      * <li>If at a repeating segment: if segment name matches direction then
250      * next means next rep, otherwise next means next "position".</li>
251      * <li>If at a segment within a group (not at the end of the group), next
252      * "position" means next sibling</li>
253      * <li>If at the end of a group: If name of group or any of its "first
254      * decendents" matches direction, then next position means next rep of
255      * group. Otherwise if direction matches name of next sibling of the group,
256      * or any of its first descendents, next position means next sibling of the
257      * group. Otherwise, next means a new segment added to the group (with a
258      * name that matches "direction").</li>
259      * <li>"First descendents" means first child, or first child of the first
260      * child, or first child of the first child of the first child, etc.</li>
261      * </ol>
262      */
263     @Override
264     public Structure next() {
265         if (!hasNext()) {
266             throw new NoSuchElementException("No more nodes in message");
267         }
268 
269         Structure currentStructure = navigateToStructure(myCurrentDefinitionPath);
270 
271         clearNext();
272         return currentStructure;
273     }
274 
275     private Structure navigateToStructure(List<Position> theDefinitionPath) throws Error {
276         Structure currentStructure = null;
277         for (Position next : theDefinitionPath) {
278             if (currentStructure == null) {
279                 currentStructure = myMessage;
280             } else {
281                 try {
282                     IStructureDefinition structureDefinition = next.getStructureDefinition();
283                     Group/uhn/hl7v2/model/Group.html#Group">Group currentStructureGroup = (Group) currentStructure;
284                     String nextStructureName = structureDefinition.getNameAsItAppearsInParent();
285                     currentStructure = currentStructureGroup.get(nextStructureName, next.getRepNumber());
286                 } catch (HL7Exception e) {
287                     throw new Error("Failed to retrieve structure: ", e);
288                 }
289             }
290         }
291         return currentStructure;
292     }
293 
294     /** Not supported */
295     @Override
296     public void remove() {
297         throw new UnsupportedOperationException("Can't remove a node from a message");
298     }
299 
300     public String getDirection() {
301         return this.myDirection;
302     }
303 
304     public void setDirection(String direction) {
305         clearNext();
306         this.myDirection = direction;
307     }
308 
309     private void clearNext() {
310         myNextIsSet = false;
311     }
312 
313     /**
314      * A structure position within a message.
315      */
316     public static class Position {
317         private IStructureDefinition myStructureDefinition;
318         private int myRepNumber;
319 
320         public IStructureDefinition getStructureDefinition() {
321             return myStructureDefinition;
322         }
323 
324         public void resetRepNumber() {
325             myRepNumber = -1;
326         }
327 
328         public void setStructureDefinition(IStructureDefinition theStructureDefinition) {
329             myStructureDefinition = theStructureDefinition;
330         }
331 
332         public int getRepNumber() {
333             return myRepNumber;
334         }
335 
336         public Position(IStructureDefinition theStructureDefinition, int theRepNumber) {
337             myStructureDefinition = theStructureDefinition;
338             myRepNumber = theRepNumber;
339         }
340 
341         public void incrementRep() {
342             myRepNumber++;
343         }
344 
345         /** @see Object#equals */
346         public boolean equals(Object o) {
347             boolean equals = false;
348             if (o instanceof Position) {
349                 Position p = (Position) o;
350                 if (p.myStructureDefinition.equals(myStructureDefinition) && p.myRepNumber == myRepNumber)
351                     equals = true;
352             }
353             return equals;
354         }
355 
356         /** @see Object#hashCode */
357         public int hashCode() {
358             return myStructureDefinition.hashCode() + myRepNumber;
359         }
360 
361         public String toString() {
362             StringBuilder ret = new StringBuilder();
363 
364             if (myStructureDefinition.getParent() != null) {
365                 ret.append(myStructureDefinition.getParent().getName());
366             } else {
367                 ret.append("Root");
368             }
369 
370             ret.append(":");
371             ret.append(myStructureDefinition.getName());
372             ret.append("(");
373             ret.append(myRepNumber);
374             ret.append(")");
375             return ret.toString();
376         }
377     }
378 
379     /**
380      * Must be called after {@link #next()}
381      */
382     public int getNextIndexWithinParent() {
383         return getCurrentPosition().getStructureDefinition().getPosition();
384     }
385 }