Coverage Report - ca.uhn.hl7v2.parser.MessageIterator
 
Classes in this File Line Coverage Branch Coverage Complexity
MessageIterator
89%
109/122
91%
68/74
3.522
MessageIterator$1
100%
1/1
N/A
3.522
MessageIterator$Position
43%
13/30
0%
0/10
3.522
 
 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  0
 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  586
     private List<Position> myCurrentDefinitionPath = new ArrayList<Position>();
 38  
 
 39  1
     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  586
     public MessageIterator(Message start, IStructureDefinition startDefinition, String direction, boolean handleUnexpectedSegments) {
 51  586
         this.myMessage = start;
 52  586
         this.myDirection = direction;
 53  586
         this.myHandleUnexpectedSegments = handleUnexpectedSegments;
 54  586
         this.myCurrentDefinitionPath.add(new Position(startDefinition, -1));
 55  586
     }
 56  
 
 57  
     private Position getCurrentPosition() {
 58  7318
         return getTail(myCurrentDefinitionPath);
 59  
     }
 60  
 
 61  
     private Position getTail(List<Position> theDefinitionPath) {
 62  7633
         return theDefinitionPath.get(theDefinitionPath.size() - 1);
 63  
     }
 64  
 
 65  
     private List<Position> popUntilMatchFound(List<Position> theDefinitionPath) {
 66  315
         theDefinitionPath = new ArrayList<Position>(theDefinitionPath.subList(0, theDefinitionPath.size() - 1));
 67  
 
 68  315
         Position newCurrentPosition = getTail(theDefinitionPath);
 69  315
         IStructureDefinition newCurrentStructureDefinition = newCurrentPosition.getStructureDefinition();
 70  
 
 71  315
         if (newCurrentStructureDefinition.getAllPossibleFirstChildren().contains(myDirection)) {
 72  138
             return theDefinitionPath;
 73  
         }
 74  
 
 75  177
         if (newCurrentStructureDefinition.isFinalChildOfParent()) {
 76  65
             if (theDefinitionPath.size() > 1) {
 77  36
                 return popUntilMatchFound(theDefinitionPath); // recurse
 78  
             } else {
 79  29
                     log.debug("Popped to root of message and did not find a match for {}", myDirection);
 80  29
                 return null;
 81  
             }
 82  
         }
 83  
 
 84  112
         return theDefinitionPath;
 85  
     }
 86  
 
 87  
     /**
 88  
      * Returns true if another object exists in the iteration sequence.
 89  
      */
 90  
     public boolean hasNext() {
 91  
 
 92  4191
         log.trace("hasNext() for direction {}", myDirection);
 93  4191
         if (myDirection == null) {
 94  0
             throw new IllegalStateException("Direction not set");
 95  
         }
 96  
 
 97  10943
         while (!myNextIsSet) {
 98  
 
 99  6753
             Position currentPosition = getCurrentPosition();
 100  
 
 101  6753
             log.trace("hasNext() current position: {}", currentPosition);
 102  
 
 103  6753
             IStructureDefinition structureDefinition = currentPosition.getStructureDefinition();
 104  
             
 105  6753
             if (myMessage.getParser().getParserConfiguration().isNonGreedyMode()) {
 106  40
                     IStructureDefinition nonGreedyPosition = couldBeNotGreedy();
 107  40
                     if (nonGreedyPosition != null) {
 108  4
                             log.info("Found non greedy parsing choice, moving to {}", nonGreedyPosition.getName());
 109  12
                             while (getCurrentPosition().getStructureDefinition() != nonGreedyPosition) {
 110  8
                                     myCurrentDefinitionPath.remove(myCurrentDefinitionPath.size() - 1);
 111  
                             }
 112  
                     }
 113  
             }
 114  
             
 115  6753
             if (structureDefinition.isSegment() && structureDefinition.getName().startsWith(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) {
 116  1911
                 myNextIsSet = true;
 117  1911
                 currentPosition.incrementRep();
 118  4842
             } else if (structureDefinition.isSegment() && structureDefinition.getNextLeaf() == null
 119  
                     && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) {
 120  100
                 if (!myHandleUnexpectedSegments) {
 121  0
                     return false;
 122  
                 }
 123  100
                 addNonStandardSegmentAtCurrentPosition();
 124  4742
             } else if (structureDefinition.hasChildren() && structureDefinition.getAllPossibleFirstChildren().contains(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) {
 125  1565
                 currentPosition.incrementRep();
 126  1565
                 myCurrentDefinitionPath.add(new Position(structureDefinition.getFirstChild(), -1));
 127  3177
             } else if (!structureDefinition.hasChildren() && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) {
 128  56
                 if (!myHandleUnexpectedSegments) {
 129  0
                     return false;
 130  
                 }
 131  56
                 addNonStandardSegmentAtCurrentPosition();
 132  
                 // } else if (structureDefinition.isMessage()) {
 133  
                 // if (!handleUnexpectedSegments) {
 134  
                 // return false;
 135  
                 // }
 136  
                 // addNonStandardSegmentAtCurrentPosition();
 137  3121
             } else if (structureDefinition.isFinalChildOfParent()) {
 138  279
                 List<Position> newDefinitionPath = popUntilMatchFound(myCurrentDefinitionPath);
 139  279
                 if (newDefinitionPath != null) {
 140  
                     // found match
 141  250
                     myCurrentDefinitionPath = newDefinitionPath;
 142  
                 } else {
 143  29
                     if (!myHandleUnexpectedSegments) {
 144  0
                         return false;
 145  
                     }
 146  29
                     addNonStandardSegmentAtCurrentPosition();
 147  
                 }
 148  279
             } else {
 149  2842
                 currentPosition.setStructureDefinition(structureDefinition.getNextSibling());
 150  2842
                 currentPosition.resetRepNumber();
 151  
             }
 152  
 
 153  6752
         }
 154  
 
 155  4190
         return true;
 156  
     }
 157  
 
 158  
     /**
 159  
      * @see ParserConfiguration#setNonGreedyMode(boolean)
 160  
      */
 161  
     private IStructureDefinition couldBeNotGreedy() {
 162  109
             for (int i = myCurrentDefinitionPath.size() - 1; i >= 1; i--) {
 163  73
                     Position position = myCurrentDefinitionPath.get(i);
 164  73
                     IStructureDefinition curPos = position.getStructureDefinition();
 165  73
                     if (curPos.getPosition() > 0) {
 166  52
                             IStructureDefinition parent = curPos.getParent();
 167  52
                                 if (parent.isRepeating() && parent.getAllPossibleFirstChildren().contains(myDirection)) {
 168  4
                                     return parent;
 169  
                             }
 170  
                     }
 171  
                     
 172  
             }
 173  
             
 174  36
                 return null;
 175  
         }
 176  
 
 177  
         private void addNonStandardSegmentAtCurrentPosition() throws Error {
 178  185
             log.debug("Creating non standard segment {} on group: {}", 
 179  
                             myDirection, getCurrentPosition().getStructureDefinition().getParent().getName());
 180  
         
 181  
             List<Position> parentDefinitionPath;
 182  
         Group parentStructure;
 183  
         
 184  1
         switch (myMessage.getParser().getParserConfiguration().getUnexpectedSegmentBehaviour()) {
 185  
         case ADD_INLINE:
 186  
         default:
 187  181
                 parentDefinitionPath = new ArrayList<Position>(myCurrentDefinitionPath.subList(0, myCurrentDefinitionPath.size() - 1));
 188  181
                 parentStructure = (Group) navigateToStructure(parentDefinitionPath);
 189  181
                 break;
 190  
         case DROP_TO_ROOT:
 191  3
                 parentDefinitionPath = new ArrayList<Position>(myCurrentDefinitionPath.subList(0, 1));
 192  3
                 parentStructure = myMessage;
 193  3
                 myCurrentDefinitionPath = myCurrentDefinitionPath.subList(0, 2);
 194  3
                 break;
 195  
         case THROW_HL7_EXCEPTION:
 196  1
                 throw new Error(new HL7Exception("Found unknown segment: " + myDirection));
 197  
         }
 198  
         
 199  
         
 200  
         // Current position within parent
 201  184
         Position currentPosition = getCurrentPosition();
 202  184
                 String nameAsItAppearsInParent = currentPosition.getStructureDefinition().getNameAsItAppearsInParent();
 203  
 
 204  184
                 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  184
                 String[] currentNames = parentStructure.getNames();
 211  184
                 if (index < currentNames.length && currentNames[index].startsWith(myDirection)) {
 212  2
                         newSegmentName = currentNames[index];
 213  
                 } else { 
 214  
                 try {
 215  182
                     newSegmentName = parentStructure.addNonstandardSegment(myDirection, index);
 216  0
                 } catch (HL7Exception e) {
 217  0
                     throw new Error("Unable to add nonstandard segment " + myDirection + ": ", e);
 218  182
                 }
 219  
             }
 220  
                 
 221  184
         IStructureDefinition previousSibling = getCurrentPosition().getStructureDefinition();
 222  184
         IStructureDefinition parentStructureDefinition = parentDefinitionPath.get(parentDefinitionPath.size() - 1).getStructureDefinition();
 223  184
         NonStandardStructureDefinition nextDefinition = new NonStandardStructureDefinition(parentStructureDefinition, previousSibling, newSegmentName, index);
 224  184
         myCurrentDefinitionPath = parentDefinitionPath;
 225  184
         myCurrentDefinitionPath.add(new Position(nextDefinition, 0));
 226  
 
 227  184
         myNextIsSet = true;
 228  184
     }
 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  2095
         if (!hasNext()) {
 260  0
             throw new NoSuchElementException("No more nodes in message");
 261  
         }
 262  
 
 263  2095
         Structure currentStructure = navigateToStructure(myCurrentDefinitionPath);
 264  
 
 265  2095
         clearNext();
 266  2095
         return currentStructure;
 267  
     }
 268  
 
 269  
     private Structure navigateToStructure(List<Position> theDefinitionPath) throws Error {
 270  2276
         Structure currentStructure = null;
 271  2276
         for (Position next : theDefinitionPath) {
 272  6172
             if (currentStructure == null) {
 273  2276
                 currentStructure = myMessage;
 274  
             } else {
 275  
                 try {
 276  3896
                     IStructureDefinition structureDefinition = next.getStructureDefinition();
 277  3896
                     Group currentStructureGroup = (Group) currentStructure;
 278  3896
                     String nextStructureName = structureDefinition.getNameAsItAppearsInParent();
 279  3896
                     currentStructure = currentStructureGroup.get(nextStructureName, next.getRepNumber());
 280  0
                 } catch (HL7Exception e) {
 281  0
                     throw new Error("Failed to retrieve structure: ", e);
 282  3896
                 }
 283  
             }
 284  6172
         }
 285  2276
         return currentStructure;
 286  
     }
 287  
 
 288  
     /** Not supported */
 289  
     public void remove() {
 290  0
         throw new UnsupportedOperationException("Can't remove a node from a message");
 291  
     }
 292  
 
 293  
     public String getDirection() {
 294  0
         return this.myDirection;
 295  
     }
 296  
 
 297  
     public void setDirection(String direction) {
 298  2096
         clearNext();
 299  2096
         this.myDirection = direction;
 300  2096
     }
 301  
 
 302  
     private void clearNext() {
 303  4191
         myNextIsSet = false;
 304  4191
     }
 305  
 
 306  
     /**
 307  
      * A structure position within a message.
 308  
      */
 309  
     public static class Position {
 310  
         private IStructureDefinition myStructureDefinition;
 311  2335
         private int myRepNumber = -1;
 312  
 
 313  
         public IStructureDefinition getStructureDefinition() {
 314  11786
             return myStructureDefinition;
 315  
         }
 316  
 
 317  
         public void resetRepNumber() {
 318  2842
             myRepNumber = -1;
 319  2842
         }
 320  
 
 321  
         public void setStructureDefinition(IStructureDefinition theStructureDefinition) {
 322  2842
             myStructureDefinition = theStructureDefinition;
 323  2842
         }
 324  
 
 325  
         public int getRepNumber() {
 326  6630
             return myRepNumber;
 327  
         }
 328  
 
 329  2335
         public Position(IStructureDefinition theStructureDefinition, int theRepNumber) {
 330  2335
             myStructureDefinition = theStructureDefinition;
 331  2335
             myRepNumber = theRepNumber;
 332  2335
         }
 333  
 
 334  
         public void incrementRep() {
 335  3476
             myRepNumber++;
 336  3476
         }
 337  
 
 338  
         /** @see Object#equals */
 339  
         public boolean equals(Object o) {
 340  0
             boolean equals = false;
 341  0
             if (o != null && o instanceof Position) {
 342  0
                 Position p = (Position) o;
 343  0
                 if (p.myStructureDefinition.equals(myStructureDefinition) && p.myRepNumber == myRepNumber)
 344  0
                     equals = true;
 345  
             }
 346  0
             return equals;
 347  
         }
 348  
 
 349  
         /** @see Object#hashCode */
 350  
         public int hashCode() {
 351  0
             return myStructureDefinition.hashCode() + myRepNumber;
 352  
         }
 353  
 
 354  
         public String toString() {
 355  0
             StringBuilder ret = new StringBuilder();
 356  
 
 357  0
             if (myStructureDefinition.getParent() != null) {
 358  0
                 ret.append(myStructureDefinition.getParent().getName());
 359  
             } else {
 360  0
                 ret.append("Root");
 361  
             }
 362  
 
 363  0
             ret.append(":");
 364  0
             ret.append(myStructureDefinition.getName());
 365  0
             ret.append("(");
 366  0
             ret.append(myRepNumber);
 367  0
             ret.append(")");
 368  0
             return ret.toString();
 369  
         }
 370  
     }
 371  
 
 372  
     /**
 373  
      * Must be called after {@link #next()}
 374  
      */
 375  
     public int getNextIndexWithinParent() {
 376  0
         return getCurrentPosition().getStructureDefinition().getPosition();
 377  
     }
 378  
 }