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 Message myMessage;
34      private String myDirection;
35      private boolean myNextIsSet;
36      private boolean myHandleUnexpectedSegments;
37      private List<Position> myCurrentDefinitionPath = new ArrayList<Position>();
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<Position>(theDefinitionPath.subList(0, theDefinitionPath.size() - 1));
67  
68          Position newCurrentPosition = getTail(theDefinitionPath);
69          IStructureDefinition newCurrentStructureDefinition = newCurrentPosition.getStructureDefinition();
70  
71          if (newCurrentStructureDefinition.getAllPossibleFirstChildren().contains(myDirection)) {
72              return theDefinitionPath;
73          }
74  
75          if (newCurrentStructureDefinition.isFinalChildOfParent()) {
76              if (theDefinitionPath.size() > 1) {
77                  return popUntilMatchFound(theDefinitionPath); // recurse
78              } else {
79              	log.debug("Popped to root of message and did not find a match for {}", myDirection);
80                  return null;
81              }
82          }
83  
84          return theDefinitionPath;
85      }
86  
87      /**
88       * Returns true if another object exists in the iteration sequence.
89       */
90      public boolean hasNext() {
91  
92          log.trace("hasNext() for direction {}", myDirection);
93          if (myDirection == null) {
94              throw new IllegalStateException("Direction not set");
95          }
96  
97          while (!myNextIsSet) {
98  
99              Position currentPosition = getCurrentPosition();
100 
101             log.trace("hasNext() current position: {}", currentPosition);
102 
103             IStructureDefinition structureDefinition = currentPosition.getStructureDefinition();
104             
105             if (myMessage.getParser().getParserConfiguration().isNonGreedyMode()) {
106             	IStructureDefinition nonGreedyPosition = couldBeNotGreedy();
107             	if (nonGreedyPosition != null) {
108             		log.info("Found non greedy parsing choice, moving to {}", nonGreedyPosition.getName());
109             		while (getCurrentPosition().getStructureDefinition() != nonGreedyPosition) {
110             			myCurrentDefinitionPath.remove(myCurrentDefinitionPath.size() - 1);
111             		}
112             	}
113             }
114             
115             if (structureDefinition.isSegment() && structureDefinition.getName().startsWith(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) {
116                 myNextIsSet = true;
117                 currentPosition.incrementRep();
118             } else if (structureDefinition.isSegment() && structureDefinition.getNextLeaf() == null
119                     && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) {
120                 if (!myHandleUnexpectedSegments) {
121                     return false;
122                 }
123                 addNonStandardSegmentAtCurrentPosition();
124             } else if (structureDefinition.hasChildren() && structureDefinition.getAllPossibleFirstChildren().contains(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) {
125                 currentPosition.incrementRep();
126                 myCurrentDefinitionPath.add(new Position(structureDefinition.getFirstChild(), -1));
127             } else if (!structureDefinition.hasChildren() && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) {
128                 if (!myHandleUnexpectedSegments) {
129                     return false;
130                 }
131                 addNonStandardSegmentAtCurrentPosition();
132                 // } else if (structureDefinition.isMessage()) {
133                 // if (!handleUnexpectedSegments) {
134                 // return false;
135                 // }
136                 // addNonStandardSegmentAtCurrentPosition();
137             } else if (structureDefinition.isFinalChildOfParent()) {
138                 List<Position> newDefinitionPath = popUntilMatchFound(myCurrentDefinitionPath);
139                 if (newDefinitionPath != null) {
140                     // found match
141                     myCurrentDefinitionPath = newDefinitionPath;
142                 } else {
143                     if (!myHandleUnexpectedSegments) {
144                         return false;
145                     }
146                     addNonStandardSegmentAtCurrentPosition();
147                 }
148             } else {
149                 currentPosition.setStructureDefinition(structureDefinition.getNextSibling());
150                 currentPosition.resetRepNumber();
151             }
152 
153         }
154 
155         return true;
156     }
157 
158     /**
159      * @see ParserConfiguration#setNonGreedyMode(boolean)
160      */
161     private IStructureDefinition couldBeNotGreedy() {
162     	for (int i = myCurrentDefinitionPath.size() - 1; i >= 1; i--) {
163     		Position position = myCurrentDefinitionPath.get(i);
164 	    	IStructureDefinition curPos = position.getStructureDefinition();
165 	    	if (curPos.getPosition() > 0) {
166 	    		IStructureDefinition parent = curPos.getParent();
167 				if (parent.isRepeating() && parent.getAllPossibleFirstChildren().contains(myDirection)) {
168 	    			return parent;
169 	    		}
170 	    	}
171 	    	
172     	}
173     	
174 		return null;
175 	}
176 
177 	private void addNonStandardSegmentAtCurrentPosition() throws Error {
178     	log.debug("Creating non standard segment {} on group: {}", 
179     			myDirection, getCurrentPosition().getStructureDefinition().getParent().getName());
180         
181     	List<Position> parentDefinitionPath;
182         Group parentStructure;
183         
184         switch (myMessage.getParser().getParserConfiguration().getUnexpectedSegmentBehaviour()) {
185         case ADD_INLINE:
186         default:
187         	parentDefinitionPath = new ArrayList<Position>(myCurrentDefinitionPath.subList(0, myCurrentDefinitionPath.size() - 1));
188         	parentStructure = (Group) navigateToStructure(parentDefinitionPath);
189         	break;
190         case DROP_TO_ROOT:
191         	parentDefinitionPath = new ArrayList<Position>(myCurrentDefinitionPath.subList(0, 1));
192         	parentStructure = myMessage;
193         	myCurrentDefinitionPath = myCurrentDefinitionPath.subList(0, 2);
194         	break;
195         case THROW_HL7_EXCEPTION:
196         	throw new Error(new HL7Exception("Found unknown segment: " + myDirection));
197         }
198         
199         
200         // Current position within parent
201         Position currentPosition = getCurrentPosition();
202 		String nameAsItAppearsInParent = currentPosition.getStructureDefinition().getNameAsItAppearsInParent();
203 
204 		int index = Arrays.asList(parentStructure.getNames()).indexOf(nameAsItAppearsInParent) + 1;
205 		
206         String newSegmentName;
207 		
208 		// Check if the structure already has a non-standard segment in the appropriate
209 		// position
210 		String[] currentNames = parentStructure.getNames();
211 		if (index < currentNames.length && currentNames[index].startsWith(myDirection)) {
212 			newSegmentName = currentNames[index];
213 		} else { 
214 	        try {
215 	            newSegmentName = parentStructure.addNonstandardSegment(myDirection, index);
216 	        } catch (HL7Exception e) {
217 	            throw new Error("Unable to add nonstandard segment " + myDirection + ": ", e);
218 	        }
219 	    }
220 		
221         IStructureDefinition previousSibling = getCurrentPosition().getStructureDefinition();
222         IStructureDefinition parentStructureDefinition = parentDefinitionPath.get(parentDefinitionPath.size() - 1).getStructureDefinition();
223         NonStandardStructureDefinition nextDefinition = new NonStandardStructureDefinition(parentStructureDefinition, previousSibling, newSegmentName, index);
224         myCurrentDefinitionPath = parentDefinitionPath;
225         myCurrentDefinitionPath.add(new Position(nextDefinition, 0));
226 
227         myNextIsSet = true;
228     }
229 
230     /**
231      * <p>
232      * Returns the next node in the message. Sometimes the next node is
233      * ambiguous. For example at the end of a repeating group, the next node may
234      * be the first segment in the next repetition of the group, or the next
235      * sibling, or an undeclared segment locally added to the group's end. Cases
236      * like this are disambiguated using getDirection(), which returns the name
237      * of the structure that we are "iterating towards". Usually we are
238      * "iterating towards" a segment of a certain name because we have a segment
239      * string that we would like to parse into that node. Here are the rules:
240      * </p>
241      * <ol>
242      * <li>If at a group, next means first child.</li>
243      * <li>If at a non-repeating segment, next means next "position"</li>
244      * <li>If at a repeating segment: if segment name matches direction then
245      * next means next rep, otherwise next means next "position".</li>
246      * <li>If at a segment within a group (not at the end of the group), next
247      * "position" means next sibling</li>
248      * <li>If at the end of a group: If name of group or any of its "first
249      * decendents" matches direction, then next position means next rep of
250      * group. Otherwise if direction matches name of next sibling of the group,
251      * or any of its first descendents, next position means next sibling of the
252      * group. Otherwise, next means a new segment added to the group (with a
253      * name that matches "direction").</li>
254      * <li>"First descendents" means first child, or first child of the first
255      * child, or first child of the first child of the first child, etc.</li>
256      * </ol>
257      */
258     public Structure next() {
259         if (!hasNext()) {
260             throw new NoSuchElementException("No more nodes in message");
261         }
262 
263         Structure currentStructure = navigateToStructure(myCurrentDefinitionPath);
264 
265         clearNext();
266         return currentStructure;
267     }
268 
269     private Structure navigateToStructure(List<Position> theDefinitionPath) throws Error {
270         Structure currentStructure = null;
271         for (Position next : theDefinitionPath) {
272             if (currentStructure == null) {
273                 currentStructure = myMessage;
274             } else {
275                 try {
276                     IStructureDefinition structureDefinition = next.getStructureDefinition();
277                     Group currentStructureGroup = (Group) currentStructure;
278                     String nextStructureName = structureDefinition.getNameAsItAppearsInParent();
279                     currentStructure = currentStructureGroup.get(nextStructureName, next.getRepNumber());
280                 } catch (HL7Exception e) {
281                     throw new Error("Failed to retrieve structure: ", e);
282                 }
283             }
284         }
285         return currentStructure;
286     }
287 
288     /** Not supported */
289     public void remove() {
290         throw new UnsupportedOperationException("Can't remove a node from a message");
291     }
292 
293     public String getDirection() {
294         return this.myDirection;
295     }
296 
297     public void setDirection(String direction) {
298         clearNext();
299         this.myDirection = direction;
300     }
301 
302     private void clearNext() {
303         myNextIsSet = false;
304     }
305 
306     /**
307      * A structure position within a message.
308      */
309     public static class Position {
310         private IStructureDefinition myStructureDefinition;
311         private int myRepNumber = -1;
312 
313         public IStructureDefinition getStructureDefinition() {
314             return myStructureDefinition;
315         }
316 
317         public void resetRepNumber() {
318             myRepNumber = -1;
319         }
320 
321         public void setStructureDefinition(IStructureDefinition theStructureDefinition) {
322             myStructureDefinition = theStructureDefinition;
323         }
324 
325         public int getRepNumber() {
326             return myRepNumber;
327         }
328 
329         public Position(IStructureDefinition theStructureDefinition, int theRepNumber) {
330             myStructureDefinition = theStructureDefinition;
331             myRepNumber = theRepNumber;
332         }
333 
334         public void incrementRep() {
335             myRepNumber++;
336         }
337 
338         /** @see Object#equals */
339         public boolean equals(Object o) {
340             boolean equals = false;
341             if (o != null && o instanceof Position) {
342                 Position p = (Position) o;
343                 if (p.myStructureDefinition.equals(myStructureDefinition) && p.myRepNumber == myRepNumber)
344                     equals = true;
345             }
346             return equals;
347         }
348 
349         /** @see Object#hashCode */
350         public int hashCode() {
351             return myStructureDefinition.hashCode() + myRepNumber;
352         }
353 
354         public String toString() {
355             StringBuilder ret = new StringBuilder();
356 
357             if (myStructureDefinition.getParent() != null) {
358                 ret.append(myStructureDefinition.getParent().getName());
359             } else {
360                 ret.append("Root");
361             }
362 
363             ret.append(":");
364             ret.append(myStructureDefinition.getName());
365             ret.append("(");
366             ret.append(myRepNumber);
367             ret.append(")");
368             return ret.toString();
369         }
370     }
371 
372     /**
373      * Must be called after {@link #next()}
374      */
375     public int getNextIndexWithinParent() {
376         return getCurrentPosition().getStructureDefinition().getPosition();
377     }
378 }