View Javadoc
1   package ca.uhn.hl7v2.util;
2   
3   import java.util.NoSuchElementException;
4   
5   import org.slf4j.Logger;
6   import org.slf4j.LoggerFactory;
7   
8   import ca.uhn.hl7v2.HL7Exception;
9   import ca.uhn.hl7v2.model.Group;
10  import ca.uhn.hl7v2.model.Message;
11  import ca.uhn.hl7v2.model.Segment;
12  import ca.uhn.hl7v2.model.Structure;
13  
14  /**
15   * Iterates over all defined nodes (ie segments, groups) in a message, 
16   * regardless of whether they have been instantiated previously.  This is a 
17   * tricky process, because the number of nodes is infinite, due to infinitely 
18   * repeating segments and groups.  See <code>next()</code> for details on 
19   * how this is handled. 
20   * 
21   * This implementation assumes that the first segment in each group is present (as per
22   * HL7 rules).  Specifically, when looking for a segment location, an empty group that has 
23   * a spot for the segment will be overlooked if there is anything else before that spot. 
24   * This may result in surprising (but sensible) behaviour if a message is missing the 
25   * first segment in a group. 
26   *  
27   * @author Bryan Tripp
28   */
29  public class MessageIterator implements java.util.Iterator<Structure> {
30  
31      private Structure currentStructure; 
32      private String direction;
33      private Position next;
34      private final boolean handleUnexpectedSegments;
35      
36      private static final Logger log = LoggerFactory.getLogger(MessageIterator.class);
37      
38      /* may add configurability later ... 
39      private boolean findUpToFirstRequired;
40      private boolean findFirstDescendentsOnly;
41      
42      public static final String WHOLE_GROUP;
43      public static final String FIRST_DESCENDENTS_ONLY;
44      public static final String UP_TO_FIRST_REQUIRED;
45      */
46       
47      /** Creates a new instance of MessageIterator */
48      public MessageIterator(Structure start, String direction, boolean handleUnexpectedSegments) {
49          this.currentStructure = start;
50          this.direction = direction;
51          this.handleUnexpectedSegments = handleUnexpectedSegments;
52      }
53      
54      /* for configurability (maybe to add later, replacing hard-coded options
55        in nextFromEndOfGroup) ... 
56      public void setSearchLevel(String level) {
57          if (WHOLE_GROUP.equals(level)) {
58              this.findUpToFirstRequired = false;
59              this.findFirstDescendentsOnly = false;
60          } else if (FIRST_DESCENDENTS_ONLY.equals(level)) {
61              this.findUpToFirstRequired = false;
62              this.findFirstDescendentsOnly = true;
63          } else if (UP_TO_FIRST_REQUIRED.equals(level)) {
64              this.findUpToFirstRequired = true;
65              this.findFirstDescendentsOnly = false;
66          } else {
67              throw IllegalArgumentException(level + " is not a valid search level.  Should be WHOLE_GROUP, etc.");
68          }     
69      }
70      
71      public String getSearchLevel() {
72          String level = WHOLE_GROUP;
73          if (this.findFirstDescendentsOnly) {
74              level = FIRST_DESCENDENTS_ONLY;
75          } else if (this.findUpTpFirstRequired) {
76              level = UP_TO_FIRST_REQUIRED;
77          }
78          return level;
79      }*/
80       
81      
82      /**
83       * Returns true if another object exists in the iteration sequence.  
84       */
85      public boolean hasNext() {
86          boolean has = true;
87          if (next == null) {
88              if (Group.class.isAssignableFrom(currentStructure.getClass())) {
89                  groupNext((Group) currentStructure);
90              } else {
91                  Group parent = currentStructure.getParent();
92                  Index i = getIndex(parent, currentStructure);
93                  Position currentPosition = new Position(parent, i);
94                  
95                  try {                    
96                      if (parent.isRepeating(i.name) && currentStructure.getName().equals(direction)) {
97                          nextRep(currentPosition);
98                      } else {
99                          has = nextPosition(currentPosition, this.direction, this.handleUnexpectedSegments);
100                     }
101                 } catch (HL7Exception e) {
102                     throw new Error("HL7Exception arising from bad index: " + e.getMessage());
103                 }
104             }
105         }
106         log.debug("MessageIterator.hasNext() in direction {}? {}", direction, has);
107         return has;
108     }
109     
110     /**
111      * Sets next to the first child of the given group (iteration 
112      * always proceeds from group to first child). 
113      */
114     private void groupNext(Group current) {
115         next = new Position(current, current.getNames()[0], 0);
116     }
117     
118     /**
119      * Sets next to the next repetition of the current structure.  
120      */ 
121     private void nextRep(Position current) {        
122         next = new Position(current.parent, current.index.name, current.index.rep + 1);
123     }
124     
125     /**
126      * Sets this.next to the next position in the message (from the given position), 
127      * which could be the next sibling, a new segment, or the next rep 
128      * of the parent.  See next() for details. 
129      */
130     private boolean nextPosition(Position currPos, String direction, boolean makeNewSegmentIfNeeded) throws HL7Exception {
131         boolean nextExists = true;
132         if (isLast(currPos)) {
133             nextExists = nextFromGroupEnd(currPos, direction, makeNewSegmentIfNeeded);
134         } else {
135             nextSibling(currPos);
136         }
137         return nextExists;
138     }
139     
140     /** Navigates from end of group */
141     private boolean nextFromGroupEnd(Position currPos, String direction, boolean makeNewSegmentIfNeeded) throws HL7Exception {
142         assert isLast(currPos);
143         boolean nextExists = true;
144         
145         //the following conditional logic is a little convoluted -- its meant as an optimization 
146         // i.e. trying to avoid calling matchExistsAfterCurrentPosition
147         
148         if (!makeNewSegmentIfNeeded && Message.class.isAssignableFrom(currPos.parent.getClass())) {
149             nextExists = false;
150         } else if (!makeNewSegmentIfNeeded || matchExistsAfterPosition(currPos, direction, false, true)) {     
151             Group grandparent = currPos.parent.getParent();
152             Index parentIndex = getIndex(grandparent, currPos.parent);
153             Position parentPos = new Position(grandparent, parentIndex);
154             
155             try {
156                 boolean parentRepeats = parentPos.parent.isRepeating(parentPos.index.name);                
157                 if (parentRepeats && contains(parentPos.parent.get(parentPos.index.name, 0), direction, false, true)) {
158                     nextRep(parentPos);
159                 } else {
160                     nextExists = nextPosition(parentPos, direction, makeNewSegmentIfNeeded);
161                 }
162             } catch (HL7Exception e) {
163                 throw new Error("HL7Exception arising from bad index: " + e.getMessage());
164             }
165         } else {
166             newSegment(currPos.parent, direction);
167         }
168         return nextExists;
169     }
170     
171     /** 
172      * A match exists for the given name somewhere after the given position (in the 
173      * normal serialization order).  
174      * @param pos the message position after which to look (note that this specifies 
175      *      the message instance)
176      * @param name the name of the structure to look for
177      * @param firstDescendentsOnly only searches the first children of a group 
178      * @param upToFirstRequired only searches the children of a group up to the first 
179      *      required child (normally the first one).  This is used when we are parsing 
180      *      a message in order and looking for a place to parse a particular segment -- 
181      *      if the message is correct then it can't go after a required position of a 
182      *      different name. 
183      */
184     public static boolean matchExistsAfterPosition(Position pos, String name, boolean firstDescendentsOnly, boolean upToFirstRequired) throws HL7Exception {
185         boolean matchExists = false;
186         
187         //check next rep of self (if any)
188         if (pos.parent.isRepeating(pos.index.name)) {            
189             Structure s = pos.parent.get(pos.index.name, pos.index.rep);
190             matchExists = contains(s, name, firstDescendentsOnly, upToFirstRequired);
191         } 
192         
193         //check later siblings (if any) 
194         if (!matchExists) {
195             String[] siblings = pos.parent.getNames();
196             boolean after = false;
197             for (int i = 0; i < siblings.length && !matchExists; i++) {
198                 if (after) {
199                     matchExists = contains(pos.parent.get(siblings[i]), name, firstDescendentsOnly, upToFirstRequired);
200                     if (upToFirstRequired && pos.parent.isRequired(siblings[i])) break; 
201                 }
202                 if (pos.index.name.equals(siblings[i])) after = true;                
203             } 
204         }
205         
206         //recurse to parent (if parent is not message root)
207         if (!matchExists && !Message.class.isAssignableFrom(pos.parent.getClass())) {
208             Group grandparent = pos.parent.getParent();
209             Position parentPos = new Position(grandparent, getIndex(grandparent, pos.parent));
210             matchExists = matchExistsAfterPosition(parentPos, name, firstDescendentsOnly, upToFirstRequired);
211         }
212         log.debug("Match exists after position {} for {}? {}", pos, name, matchExists);
213         return matchExists;
214     }
215     
216     /** 
217      * Sets the next position to a new segment of the given name, within the 
218      * given group. 
219      */
220     private void newSegment(Group parent, String name) throws HL7Exception {
221         log.info("MessageIterator creating new segment: {}", name);
222         parent.addNonstandardSegment(name);
223         next = new Position(parent, parent.getNames()[parent.getNames().length-1], 0);
224     }
225     
226     /** 
227      * Determines whether the given structure matches the given name, or contains 
228      * a child that does.  
229      * @param s the structure to check 
230      * @param name the name to look for 
231      * @param firstDescendentsOnly only checks first descendents (i.e. first 
232      *      child, first child of first child, etc.)  In theory the first child 
233      *      of a group should always be present, and we don't use this method with 
234      *      subsequent children because finding the next position within a group is 
235      *      straightforward.  
236      * @param upToFirstRequired only checks first descendents and of their siblings 
237      *      up to the first required one.  This may be needed because in practice 
238      *      some first children of groups are not required.  
239      */
240     public static boolean contains(Structure s, String name, boolean firstDescendentsOnly, boolean upToFirstRequired) {
241         boolean contains = false;
242         if (Segment.class.isAssignableFrom(s.getClass())) {
243             if (s.getName().equals(name)) contains = true;            
244         } else {
245             Grouphref="../../../../ca/uhn/hl7v2/model/Group.html#Group">Group g = (Group) s;
246             String[] names = g.getNames();
247             for (int i = 0; i < names.length && !contains; i++) {
248                 try {
249                     contains = contains(g.get(names[i], 0), name, firstDescendentsOnly, upToFirstRequired);                
250                     if (firstDescendentsOnly) break;
251                     if (upToFirstRequired && g.isRequired(names[i])) break; 
252                 } catch (HL7Exception e) {
253                     throw new Error("HL7Exception due to bad index: " + e.getMessage());
254                 }
255             }
256         }
257         return contains;
258     }
259     
260     /**
261      * Tests whether the name of the given Index matches 
262      * the name of the last child of the given group. 
263      */
264     public static boolean isLast(Position p) {
265         String[] names = p.parent.getNames();
266         return names[names.length-1].equals(p.index.name);
267     }
268     
269     /**
270      * Sets the next location to the next sibling of the given 
271      * index.  
272      */
273     private void nextSibling(Position pos) {
274         String[] names = pos.parent.getNames();
275         int i = 0;
276         for (; i < names.length && !names[i].equals(pos.index.name); i++) {}
277         String nextName = names[i+1];
278         
279         this.next = new Position(pos.parent, nextName, 0);
280     }
281     
282     /**
283      * <p>Returns the next node in the message.  Sometimes the next node is 
284      * ambiguous.  For example at the end of a repeating group, the next node 
285      * may be the first segment in the next repetition of the group, or the 
286      * next sibling, or an undeclared segment locally added to the group's end.  
287      * Cases like this are disambiguated using getDirection(), which returns  
288      * the name of the structure that we are "iterating towards".  
289      * Usually we are "iterating towards" a segment of a certain name because we 
290      * have a segment string that we would like to parse into that node. 
291      * Here are the rules: </p>
292      * <ol><li>If at a group, next means first child.</li>
293      * <li>If at a non-repeating segment, next means next "position"</li>
294      * <li>If at a repeating segment: if segment name matches 
295      * direction then next means next rep, otherwise next means next "position".</li>
296      * <li>If at a segment within a group (not at the end of the group), next "position" 
297      * means next sibling</li>
298      * <li>If at the end of a group: If name of group or any of its "first 
299      * decendents" matches direction, then next position means next rep of group.  Otherwise 
300      * if direction matches name of next sibling of the group, or any of its first 
301      * descendents, next position means next sibling of the group.  Otherwise, next means a 
302      * new segment added to the group (with a name that matches "direction").  </li>
303      * <li>"First descendents" means first child, or first child of the first child, 
304      * or first child of the first child of the first child, etc. </li> </ol>
305      */
306     public Structure next() {
307         if (!hasNext()) {
308             throw new NoSuchElementException("No more nodes in message");
309         }
310         try {
311             this.currentStructure = next.parent.get(next.index.name, next.index.rep);
312         } catch (HL7Exception e) {
313             throw new NoSuchElementException("HL7Exception: " + e.getMessage());
314         }
315         clearNext();
316         return this.currentStructure;
317     }
318     
319     /** Not supported */
320     public void remove() {
321         throw new UnsupportedOperationException("Can't remove a node from a message");
322     }
323     
324     public String getDirection() {
325         return this.direction;
326     }
327     
328     public void setDirection(String direction) {
329         clearNext();
330         this.direction = direction;
331     }
332     
333     private void clearNext() {
334         next = null;
335     }
336     
337     /**
338      * Returns the index of the given structure as a child of the 
339      * given parent.  Returns null if the child isn't found. 
340      */
341     public static Index getIndex(Group parent, Structure child) {
342         Index index = null;
343         String[] names = parent.getNames();
344         findChild :
345         for (String name : names) {
346             if (name.startsWith(child.getName())) {
347                 try {
348                     Structure[] reps = parent.getAll(name);
349                     for (int j = 0; j < reps.length; j++) {
350                         if (child.equals(reps[j])) {
351                             index = new Index(name, j);
352                             break findChild;
353                         }
354                     }
355                 } catch (HL7Exception e) {
356                     log.error(e.getMessage(), e);
357                     throw new Error("Internal HL7Exception finding structure index: " + e.getMessage());
358                 }
359             }
360         }
361         return index;
362     }
363     
364     /** 
365      * An index of a child structure within a group, consisting of the name and rep of 
366      * of the child.
367      */
368     public static class Index {
369         public final String name;
370         public final int rep;
371         public Index(String name, int rep) {
372             this.name = name;
373             this.rep = rep;
374         }
375         
376         /** @see Object#equals */
377         public boolean equals(Object o) {
378             boolean equals = false;
379             if (o instanceof Index) {
380                 Index i = (Index) o;
381                 if (i.rep == rep && i.name.equals(name)) equals = true;
382             }
383             return equals;
384         }
385         
386         /** @see Object#hashCode */
387         public int hashCode() {
388             return name.hashCode() + 700 * rep;
389         }
390         
391         /** @see Object#toString */        
392         public String toString() {
393             return this.name + ":" + this.rep;
394         }
395     }
396     
397     /**
398      * A structure position within a message. 
399      */
400     public static class Position {
401         public final Group parent;
402         public final Index index;
403         public Position(Group parent, String name, int rep) {
404             this.parent = parent;
405             this.index = new Index(name, rep);
406         }
407         public Position(Group parent, Index i) {
408             this.parent = parent;
409             this.index = i;
410         }
411 
412         /** @see Object#equals */
413         public boolean equals(Object o) {
414             boolean equals = false;
415             if (o instanceof Position) {
416                 Position p = (Position) o;
417                 if (p.parent.equals(parent) && p.index.equals(index)) equals = true;
418             }
419             return equals;
420         }
421         
422         /** @see Object#hashCode */
423         public int hashCode() {
424             return parent.hashCode() + index.hashCode();
425         }
426         
427         public String toString() {
428             return parent.getName() + ":" +
429                     index.name +
430                     "(" +
431                     index.rep +
432                     ")";
433         }
434     }
435 }