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  1705
 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  1
     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  1686
     public MessageIterator(Structure start, String direction, boolean handleUnexpectedSegments) {
 49  1686
         this.currentStructure = start;
 50  1686
         this.direction = direction;
 51  1686
         this.handleUnexpectedSegments = handleUnexpectedSegments;
 52  1686
     }
 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  3422
         boolean has = true;
 87  3422
         if (next == null) {
 88  1718
             if (Group.class.isAssignableFrom(currentStructure.getClass())) {
 89  682
                 groupNext((Group) currentStructure);
 90  
             } else {
 91  1036
                 Group parent = currentStructure.getParent();
 92  1036
                 Index i = getIndex(parent, currentStructure);
 93  1036
                 Position currentPosition = new Position(parent, i);
 94  
                 
 95  
                 try {                    
 96  1036
                     if (parent.isRepeating(i.name) && currentStructure.getName().equals(direction)) {
 97  1
                         nextRep(currentPosition);
 98  
                     } else {
 99  1035
                         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  1036
                 }
 104  
             }
 105  
         }
 106  3422
         log.debug("MessageIterator.hasNext() in direction {}? {}", direction, has);
 107  3422
         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  682
         next = new Position(current, ((Group) current).getNames()[0], 0);
 116  682
     }
 117  
     
 118  
     /**
 119  
      * Sets next to the next repetition of the current structure.  
 120  
      */ 
 121  
     private void nextRep(Position current) {        
 122  2
         next = new Position(current.parent, current.index.name, current.index.rep + 1);
 123  2
     }
 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  1234
         boolean nextExists = true;
 132  1234
         if (isLast(currPos)) {
 133  204
             nextExists = nextFromGroupEnd(currPos, direction, makeNewSegmentIfNeeded);
 134  
         } else {
 135  1030
             nextSibling(currPos);
 136  
         }
 137  1234
         return nextExists;
 138  
     }
 139  
     
 140  
     /** Navigates from end of group */
 141  
     private boolean nextFromGroupEnd(Position currPos, String direction, boolean makeNewSegmentIfNeeded) throws HL7Exception {
 142  204
         assert isLast(currPos);
 143  204
         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  204
         if (!makeNewSegmentIfNeeded && Message.class.isAssignableFrom(currPos.parent.getClass())) {
 149  2
             nextExists = false;
 150  202
         } else if (!makeNewSegmentIfNeeded || matchExistsAfterPosition(currPos, direction, false, true)) {     
 151  200
             Group grandparent = currPos.parent.getParent();
 152  200
             Index parentIndex = getIndex(grandparent, currPos.parent);
 153  200
             Position parentPos = new Position(grandparent, parentIndex);
 154  
             
 155  
             try {
 156  200
                 boolean parentRepeats = parentPos.parent.isRepeating(parentPos.index.name);                
 157  200
                 if (parentRepeats && contains(parentPos.parent.get(parentPos.index.name, 0), direction, false, true)) {
 158  1
                     nextRep(parentPos);
 159  
                 } else {
 160  199
                     nextExists = nextPosition(parentPos, direction, makeNewSegmentIfNeeded);
 161  
                 }
 162  0
             } catch (HL7Exception e) {
 163  0
                 throw new Error("HL7Exception arising from bad index: " + e.getMessage());
 164  200
             }
 165  200
         } else {
 166  2
             newSegment(currPos.parent, direction);
 167  
         }
 168  204
         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  28
         boolean matchExists = false;
 186  
         
 187  
         //check next rep of self (if any)
 188  28
         if (pos.parent.isRepeating(pos.index.name)) {            
 189  11
             Structure s = pos.parent.get(pos.index.name, pos.index.rep);
 190  11
             matchExists = contains(s, name, firstDescendentsOnly, upToFirstRequired);
 191  
         } 
 192  
         
 193  
         //check later siblings (if any) 
 194  28
         if (!matchExists) {
 195  26
             String[] siblings = pos.parent.getNames();
 196  26
             boolean after = false;
 197  227
             for (int i = 0; i < siblings.length && !matchExists; i++) {
 198  206
                 if (after) {
 199  40
                     matchExists = contains(pos.parent.get(siblings[i]), name, firstDescendentsOnly, upToFirstRequired);
 200  40
                     if (upToFirstRequired && pos.parent.isRequired(siblings[i])) break; 
 201  
                 }
 202  201
                 if (pos.index.name.equals(siblings[i])) after = true;                
 203  
             } 
 204  
         }
 205  
         
 206  
         //recurse to parent (if parent is not message root)
 207  28
         if (!matchExists && !Message.class.isAssignableFrom(pos.parent.getClass())) {
 208  15
             Group grandparent = pos.parent.getParent();
 209  15
             Position parentPos = new Position(grandparent, getIndex(grandparent, pos.parent));
 210  15
             matchExists = matchExistsAfterPosition(parentPos, name, firstDescendentsOnly, upToFirstRequired);
 211  
         }
 212  28
         log.debug("Match exists after position {} for {}? {}", new Object[] {pos, name, matchExists});
 213  28
         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  2
         log.info("MessageIterator creating new segment: {}", name);
 222  2
         parent.addNonstandardSegment(name);
 223  2
         next = new Position(parent, parent.getNames()[parent.getNames().length-1], 0);
 224  2
     }
 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  168
         boolean contains = false;
 242  168
         if (Segment.class.isAssignableFrom(s.getClass())) {
 243  132
             if (s.getName().equals(name)) contains = true;            
 244  
         } else {
 245  36
             Group g = (Group) s;
 246  36
             String[] names = g.getNames();
 247  110
             for (int i = 0; i < names.length && !contains; i++) {
 248  
                 try {
 249  99
                     contains = contains(g.get(names[i], 0), name, firstDescendentsOnly, upToFirstRequired);                
 250  99
                     if (firstDescendentsOnly) break;
 251  94
                     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  74
                 }
 255  
             }
 256  
         }
 257  168
         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  1440
         String[] names = p.parent.getNames();
 266  1440
         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  1030
         String[] names = pos.parent.getNames();
 275  1030
         int i = 0;
 276  2379
         for (; i < names.length && !names[i].equals(pos.index.name); i++) {}
 277  1030
         String nextName = names[i+1];
 278  
         
 279  1030
         this.next = new Position(pos.parent, nextName, 0);
 280  1030
     }
 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  1716
         if (!hasNext()) {
 308  1
             throw new NoSuchElementException("No more nodes in message");
 309  
         }
 310  
         try {
 311  1715
             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  1715
         }
 315  1715
         clearNext();
 316  1715
         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  9
         clearNext();
 330  9
         this.direction = direction;
 331  9
     }
 332  
     
 333  
     private void clearNext() {
 334  1724
         next = null;
 335  1724
     }
 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  5276
         Index index = null;
 343  5276
         String[] names = parent.getNames();
 344  12163
         findChild : for (int i = 0; i < names.length; i++) {
 345  12162
             if (names[i].startsWith(child.getName())) {
 346  
                 try {
 347  5282
                     Structure[] reps = parent.getAll(names[i]);
 348  5288
                     for (int j = 0; j < reps.length; j++) {
 349  5281
                         if (child == reps[j]) {
 350  5275
                             index = new Index(names[i], j);
 351  5275
                             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  7
                 }
 358  
             }
 359  
         }
 360  5276
         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  6995
         public Index(String name, int rep) {
 371  6995
             this.name = name;
 372  6995
             this.rep = rep;
 373  6995
         }
 374  
         
 375  
         /** @see Object#equals */
 376  
         public boolean equals(Object o) {
 377  1
             boolean equals = false;
 378  1
             if (o != null && o instanceof Index) {
 379  1
                 Index i = (Index) o;
 380  1
                 if (i.rep == rep && i.name.equals(name)) equals = true;
 381  
             }
 382  1
             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  1719
         public Position(Group parent, String name, int rep) {
 403  1719
             this.parent = parent;
 404  1719
             this.index = new Index(name, rep);
 405  1719
         }
 406  1251
         public Position(Group parent, Index i) {
 407  1251
             this.parent = parent;
 408  1251
             this.index = i;
 409  1251
         }
 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  
 }