Coverage Report - ca.uhn.hl7v2.util.MessageIterator
 
Classes in this File Line Coverage Branch Coverage Complexity
MessageIterator
89%
108/121
94%
70/74
3.115
MessageIterator$Index
81%
9/11
50%
4/8
3.115
MessageIterator$Position
38%
8/21
0%
0/8
3.115
 
 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  8683
 public class MessageIterator implements java.util.Iterator<Structure> {
 30  
 
 31  
     private Structure currentStructure; 
 32  
     private String direction;
 33  
     private Position next;
 34  
     private boolean handleUnexpectedSegments;
 35  
     
 36  5
     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  8588
     public MessageIterator(Structure start, String direction, boolean handleUnexpectedSegments) {
 49  8588
         this.currentStructure = start;
 50  8588
         this.direction = direction;
 51  8588
         this.handleUnexpectedSegments = handleUnexpectedSegments;
 52  8588
     }
 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  17426
         boolean has = true;
 87  17426
         if (next == null) {
 88  8748
             if (Group.class.isAssignableFrom(currentStructure.getClass())) {
 89  3543
                 groupNext((Group) currentStructure);
 90  
             } else {
 91  5205
                 Group parent = currentStructure.getParent();
 92  5205
                 Index i = getIndex(parent, currentStructure);
 93  5205
                 Position currentPosition = new Position(parent, i);
 94  
                 
 95  
                 try {                    
 96  5205
                     if (parent.isRepeating(i.name) && currentStructure.getName().equals(direction)) {
 97  5
                         nextRep(currentPosition);
 98  
                     } else {
 99  5200
                         has = nextPosition(currentPosition, this.direction, this.handleUnexpectedSegments);
 100  
                     }
 101  0
                 } catch (HL7Exception e) {
 102  0
                     throw new Error("HL7Exception arising from bad index: " + e.getMessage());
 103  5205
                 }
 104  
             }
 105  
         }
 106  17426
         log.debug("MessageIterator.hasNext() in direction {}? {}", direction, has);
 107  17426
         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  3543
         next = new Position(current, current.getNames()[0], 0);
 116  3543
     }
 117  
     
 118  
     /**
 119  
      * Sets next to the next repetition of the current structure.  
 120  
      */ 
 121  
     private void nextRep(Position current) {        
 122  10
         next = new Position(current.parent, current.index.name, current.index.rep + 1);
 123  10
     }
 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  6195
         boolean nextExists = true;
 132  6195
         if (isLast(currPos)) {
 133  1020
             nextExists = nextFromGroupEnd(currPos, direction, makeNewSegmentIfNeeded);
 134  
         } else {
 135  5175
             nextSibling(currPos);
 136  
         }
 137  6195
         return nextExists;
 138  
     }
 139  
     
 140  
     /** Navigates from end of group */
 141  
     private boolean nextFromGroupEnd(Position currPos, String direction, boolean makeNewSegmentIfNeeded) throws HL7Exception {
 142  1020
         assert isLast(currPos);
 143  1020
         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  1020
         if (!makeNewSegmentIfNeeded && Message.class.isAssignableFrom(currPos.parent.getClass())) {
 149  10
             nextExists = false;
 150  1010
         } else if (!makeNewSegmentIfNeeded || matchExistsAfterPosition(currPos, direction, false, true)) {     
 151  1000
             Group grandparent = currPos.parent.getParent();
 152  1000
             Index parentIndex = getIndex(grandparent, currPos.parent);
 153  1000
             Position parentPos = new Position(grandparent, parentIndex);
 154  
             
 155  
             try {
 156  1000
                 boolean parentRepeats = parentPos.parent.isRepeating(parentPos.index.name);                
 157  1000
                 if (parentRepeats && contains(parentPos.parent.get(parentPos.index.name, 0), direction, false, true)) {
 158  5
                     nextRep(parentPos);
 159  
                 } else {
 160  995
                     nextExists = nextPosition(parentPos, direction, makeNewSegmentIfNeeded);
 161  
                 }
 162  0
             } catch (HL7Exception e) {
 163  0
                 throw new Error("HL7Exception arising from bad index: " + e.getMessage());
 164  1000
             }
 165  1000
         } else {
 166  10
             newSegment(currPos.parent, direction);
 167  
         }
 168  1020
         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  140
         boolean matchExists = false;
 186  
         
 187  
         //check next rep of self (if any)
 188  140
         if (pos.parent.isRepeating(pos.index.name)) {            
 189  55
             Structure s = pos.parent.get(pos.index.name, pos.index.rep);
 190  55
             matchExists = contains(s, name, firstDescendentsOnly, upToFirstRequired);
 191  
         } 
 192  
         
 193  
         //check later siblings (if any) 
 194  140
         if (!matchExists) {
 195  130
             String[] siblings = pos.parent.getNames();
 196  130
             boolean after = false;
 197  1135
             for (int i = 0; i < siblings.length && !matchExists; i++) {
 198  1030
                 if (after) {
 199  200
                     matchExists = contains(pos.parent.get(siblings[i]), name, firstDescendentsOnly, upToFirstRequired);
 200  200
                     if (upToFirstRequired && pos.parent.isRequired(siblings[i])) break; 
 201  
                 }
 202  1005
                 if (pos.index.name.equals(siblings[i])) after = true;                
 203  
             } 
 204  
         }
 205  
         
 206  
         //recurse to parent (if parent is not message root)
 207  140
         if (!matchExists && !Message.class.isAssignableFrom(pos.parent.getClass())) {
 208  75
             Group grandparent = pos.parent.getParent();
 209  75
             Position parentPos = new Position(grandparent, getIndex(grandparent, pos.parent));
 210  75
             matchExists = matchExistsAfterPosition(parentPos, name, firstDescendentsOnly, upToFirstRequired);
 211  
         }
 212  140
         log.debug("Match exists after position {} for {}? {}", new Object[] {pos, name, matchExists});
 213  140
         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  10
         log.info("MessageIterator creating new segment: {}", name);
 222  10
         parent.addNonstandardSegment(name);
 223  10
         next = new Position(parent, parent.getNames()[parent.getNames().length-1], 0);
 224  10
     }
 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  840
         boolean contains = false;
 242  840
         if (Segment.class.isAssignableFrom(s.getClass())) {
 243  660
             if (s.getName().equals(name)) contains = true;            
 244  
         } else {
 245  180
             Group g = (Group) s;
 246  180
             String[] names = g.getNames();
 247  550
             for (int i = 0; i < names.length && !contains; i++) {
 248  
                 try {
 249  495
                     contains = contains(g.get(names[i], 0), name, firstDescendentsOnly, upToFirstRequired);                
 250  495
                     if (firstDescendentsOnly) break;
 251  470
                     if (upToFirstRequired && g.isRequired(names[i])) break; 
 252  0
                 } catch (HL7Exception e) {
 253  0
                     throw new Error("HL7Exception due to bad index: " + e.getMessage());
 254  370
                 }
 255  
             }
 256  
         }
 257  840
         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  7225
         String[] names = p.parent.getNames();
 266  7225
         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  5175
         String[] names = pos.parent.getNames();
 275  5175
         int i = 0;
 276  11920
         for (; i < names.length && !names[i].equals(pos.index.name); i++) {}
 277  5175
         String nextName = names[i+1];
 278  
         
 279  5175
         this.next = new Position(pos.parent, nextName, 0);
 280  5175
     }
 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  8738
         if (!hasNext()) {
 308  5
             throw new NoSuchElementException("No more nodes in message");
 309  
         }
 310  
         try {
 311  8733
             this.currentStructure = next.parent.get(next.index.name, next.index.rep);
 312  0
         } catch (HL7Exception e) {
 313  0
             throw new NoSuchElementException("HL7Exception: " + e.getMessage());
 314  8733
         }
 315  8733
         clearNext();
 316  8733
         return this.currentStructure;
 317  
     }
 318  
     
 319  
     /** Not supported */
 320  
     public void remove() {
 321  0
         throw new UnsupportedOperationException("Can't remove a node from a message");
 322  
     }
 323  
     
 324  
     public String getDirection() {
 325  0
         return this.direction;
 326  
     }
 327  
     
 328  
     public void setDirection(String direction) {
 329  45
         clearNext();
 330  45
         this.direction = direction;
 331  45
     }
 332  
     
 333  
     private void clearNext() {
 334  8778
         next = null;
 335  8778
     }
 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  26638
         Index index = null;
 343  26638
         String[] names = parent.getNames();
 344  61148
         findChild : for (int i = 0; i < names.length; i++) {
 345  61143
             if (names[i].startsWith(child.getName())) {
 346  
                 try {
 347  26668
                     Structure[] reps = parent.getAll(names[i]);
 348  26698
                     for (int j = 0; j < reps.length; j++) {
 349  26663
                         if (child.equals(reps[j])) {
 350  26633
                             index = new Index(names[i], j);
 351  26633
                             break findChild; 
 352  
                         }
 353  
                     }
 354  0
                 } catch (HL7Exception e) {
 355  0
                     log.error(e.getMessage(), e);
 356  0
                     throw new Error("Internal HL7Exception finding structure index: " + e.getMessage());
 357  35
                 }
 358  
             }
 359  
         }
 360  26638
         return index;
 361  
     }
 362  
     
 363  
     /** 
 364  
      * An index of a child structure within a group, consisting of the name and rep of 
 365  
      * of the child.
 366  
      */
 367  
     public static class Index {
 368  
         public String name;
 369  
         public int rep;
 370  35391
         public Index(String name, int rep) {
 371  35391
             this.name = name;
 372  35391
             this.rep = rep;
 373  35391
         }
 374  
         
 375  
         /** @see Object#equals */
 376  
         public boolean equals(Object o) {
 377  5
             boolean equals = false;
 378  5
             if (o != null && o instanceof Index) {
 379  5
                 Index i = (Index) o;
 380  5
                 if (i.rep == rep && i.name.equals(name)) equals = true;
 381  
             }
 382  5
             return equals;
 383  
         }
 384  
         
 385  
         /** @see Object#hashCode */
 386  
         public int hashCode() {
 387  0
             return name.hashCode() + 700 * rep;
 388  
         }
 389  
         
 390  
         /** @see Object#toString */        
 391  
         public String toString() {
 392  0
             return this.name + ":" + this.rep;
 393  
         }
 394  
     }
 395  
     
 396  
     /**
 397  
      * A structure position within a message. 
 398  
      */
 399  
     public static class Position {
 400  
         public Group parent;
 401  
         public Index index;
 402  8753
         public Position(Group parent, String name, int rep) {
 403  8753
             this.parent = parent;
 404  8753
             this.index = new Index(name, rep);
 405  8753
         }
 406  6280
         public Position(Group parent, Index i) {
 407  6280
             this.parent = parent;
 408  6280
             this.index = i;
 409  6280
         }
 410  
 
 411  
         /** @see Object#equals */
 412  
         public boolean equals(Object o) {
 413  0
             boolean equals = false;
 414  0
             if (o != null && o instanceof Position) {
 415  0
                 Position p = (Position) o;
 416  0
                 if (p.parent.equals(parent) && p.index.equals(index)) equals = true;
 417  
             }
 418  0
             return equals;
 419  
         }
 420  
         
 421  
         /** @see Object#hashCode */
 422  
         public int hashCode() {
 423  0
             return parent.hashCode() + index.hashCode();
 424  
         }
 425  
         
 426  
         public String toString() {
 427  0
             StringBuffer ret = new StringBuffer(parent.getName());
 428  0
             ret.append(":");
 429  0
             ret.append(index.name);
 430  0
             ret.append("(");
 431  0
             ret.append(index.rep);
 432  0
             ret.append(")");
 433  0
             return ret.toString();           
 434  
         }
 435  
     }
 436  
 }