001package ca.uhn.hl7v2.parser; 002 003import java.util.ArrayList; 004import java.util.Arrays; 005import java.util.List; 006import java.util.NoSuchElementException; 007 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011import ca.uhn.hl7v2.HL7Exception; 012import ca.uhn.hl7v2.model.Group; 013import ca.uhn.hl7v2.model.Message; 014import ca.uhn.hl7v2.model.Structure; 015 016/** 017 * Iterates over all defined nodes (ie segments, groups) in a message, 018 * regardless of whether they have been instantiated previously. This is a 019 * tricky process, because the number of nodes is infinite, due to infinitely 020 * repeating segments and groups. See <code>next()</code> for details on how 021 * this is handled. 022 * 023 * This implementation assumes that the first segment in each group is present 024 * (as per HL7 rules). Specifically, when looking for a segment location, an 025 * empty group that has a spot for the segment will be overlooked if there is 026 * anything else before that spot. This may result in surprising (but sensible) 027 * behaviour if a message is missing the first segment in a group. 028 * 029 * @author Bryan Tripp 030 */ 031public class MessageIterator implements java.util.Iterator<Structure> { 032 033 private final Message myMessage; 034 private String myDirection; 035 private boolean myNextIsSet; 036 private final boolean myHandleUnexpectedSegments; 037 private List<Position> myCurrentDefinitionPath = new ArrayList<>(); 038 039 private static final Logger log = LoggerFactory.getLogger(MessageIterator.class); 040 041 /* 042 * may add configurability later ... private boolean findUpToFirstRequired; 043 * private boolean findFirstDescendentsOnly; 044 * 045 * public static final String WHOLE_GROUP; public static final String 046 * FIRST_DESCENDENTS_ONLY; public static final String UP_TO_FIRST_REQUIRED; 047 */ 048 049 /** Creates a new instance of MessageIterator */ 050 public MessageIterator(Message start, IStructureDefinition startDefinition, String direction, boolean handleUnexpectedSegments) { 051 this.myMessage = start; 052 this.myDirection = direction; 053 this.myHandleUnexpectedSegments = handleUnexpectedSegments; 054 this.myCurrentDefinitionPath.add(new Position(startDefinition, -1)); 055 } 056 057 private Position getCurrentPosition() { 058 return getTail(myCurrentDefinitionPath); 059 } 060 061 private Position getTail(List<Position> theDefinitionPath) { 062 return theDefinitionPath.get(theDefinitionPath.size() - 1); 063 } 064 065 private List<Position> popUntilMatchFound(List<Position> theDefinitionPath) { 066 theDefinitionPath = new ArrayList<>(theDefinitionPath.subList(0, theDefinitionPath.size() - 1)); 067 068 if (theDefinitionPath.size() == 0) { 069 return null; 070 } 071 072 Position newCurrentPosition = getTail(theDefinitionPath); 073 IStructureDefinition newCurrentStructureDefinition = newCurrentPosition.getStructureDefinition(); 074 075 if (newCurrentStructureDefinition.getAllPossibleFirstChildren().contains(myDirection)) { 076 return theDefinitionPath; 077 } 078 079 if (newCurrentStructureDefinition.isFinalChildOfParent()) { 080 if (theDefinitionPath.size() > 1) { 081 return popUntilMatchFound(theDefinitionPath); // recurse 082 } else { 083 log.debug("Popped to root of message and did not find a match for {}", myDirection); 084 return null; 085 } 086 } 087 088 return theDefinitionPath; 089 } 090 091 /** 092 * Returns true if another object exists in the iteration sequence. 093 */ 094 @Override 095 public boolean hasNext() { 096 097 log.trace("hasNext() for direction {}", myDirection); 098 if (myDirection == null) { 099 throw new IllegalStateException("Direction not set"); 100 } 101 102 while (!myNextIsSet) { 103 104 Position currentPosition = getCurrentPosition(); 105 106 log.trace("hasNext() current position: {}", currentPosition); 107 108 IStructureDefinition structureDefinition = currentPosition.getStructureDefinition(); 109 110 if (myMessage.getParser().getParserConfiguration().isNonGreedyMode()) { 111 IStructureDefinition nonGreedyPosition = couldBeNotGreedy(); 112 if (nonGreedyPosition != null) { 113 log.info("Found non greedy parsing choice, moving to {}", nonGreedyPosition.getName()); 114 while (getCurrentPosition().getStructureDefinition() != nonGreedyPosition) { 115 myCurrentDefinitionPath.remove(myCurrentDefinitionPath.size() - 1); 116 } 117 } 118 } 119 120 if (structureDefinition.isSegment() && structureDefinition.getName().startsWith(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) { 121 myNextIsSet = true; 122 currentPosition.incrementRep(); 123 } else if (structureDefinition.isSegment() && structureDefinition.getNextLeaf() == null 124 && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) { 125 if (!myHandleUnexpectedSegments) { 126 return false; 127 } 128 addNonStandardSegmentAtCurrentPosition(); 129 } else if (structureDefinition.hasChildren() && structureDefinition.getAllPossibleFirstChildren().contains(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) { 130 currentPosition.incrementRep(); 131 myCurrentDefinitionPath.add(new Position(structureDefinition.getFirstChild(), -1)); 132 } else if (!structureDefinition.hasChildren() && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) { 133 if (!myHandleUnexpectedSegments) { 134 return false; 135 } 136 addNonStandardSegmentAtCurrentPosition(); 137 // } else if (structureDefinition.isMessage()) { 138 // if (!handleUnexpectedSegments) { 139 // return false; 140 // } 141 // addNonStandardSegmentAtCurrentPosition(); 142 } else if (structureDefinition.isFinalChildOfParent()) { 143 List<Position> newDefinitionPath = popUntilMatchFound(myCurrentDefinitionPath); 144 if (newDefinitionPath != null) { 145 // found match 146 myCurrentDefinitionPath = newDefinitionPath; 147 } else { 148 if (!myHandleUnexpectedSegments) { 149 return false; 150 } 151 addNonStandardSegmentAtCurrentPosition(); 152 } 153 } else { 154 currentPosition.setStructureDefinition(structureDefinition.getNextSibling()); 155 currentPosition.resetRepNumber(); 156 } 157 158 } 159 160 return true; 161 } 162 163 /** 164 * @see ParserConfiguration#setNonGreedyMode(boolean) 165 */ 166 private IStructureDefinition couldBeNotGreedy() { 167 for (int i = myCurrentDefinitionPath.size() - 1; i >= 1; i--) { 168 Position position = myCurrentDefinitionPath.get(i); 169 IStructureDefinition curPos = position.getStructureDefinition(); 170 if (curPos.getPosition() > 0) { 171 IStructureDefinition parent = curPos.getParent(); 172 if (parent.isRepeating() && parent.getAllPossibleFirstChildren().contains(myDirection)) { 173 return parent; 174 } 175 } 176 177 } 178 179 return null; 180 } 181 182 private void addNonStandardSegmentAtCurrentPosition() throws Error { 183 log.debug("Creating non standard segment {} on group: {}", 184 myDirection, getCurrentPosition().getStructureDefinition().getParent().getName()); 185 186 List<Position> parentDefinitionPath; 187 Group parentStructure; 188 189 switch (myMessage.getParser().getParserConfiguration().getUnexpectedSegmentBehaviour()) { 190 case ADD_INLINE: 191 default: 192 parentDefinitionPath = new ArrayList<>(myCurrentDefinitionPath.subList(0, myCurrentDefinitionPath.size() - 1)); 193 parentStructure = (Group) navigateToStructure(parentDefinitionPath); 194 break; 195 case DROP_TO_ROOT: 196 parentDefinitionPath = new ArrayList<>(myCurrentDefinitionPath.subList(0, 1)); 197 parentStructure = myMessage; 198 myCurrentDefinitionPath = myCurrentDefinitionPath.subList(0, 2); 199 break; 200 case THROW_HL7_EXCEPTION: 201 throw new Error(new HL7Exception("Found unknown segment: " + myDirection)); 202 } 203 204 205 // Current position within parent 206 Position currentPosition = getCurrentPosition(); 207 String nameAsItAppearsInParent = currentPosition.getStructureDefinition().getNameAsItAppearsInParent(); 208 209 int index = Arrays.asList(parentStructure.getNames()).indexOf(nameAsItAppearsInParent) + 1; 210 211 String newSegmentName; 212 213 // Check if the structure already has a non-standard segment in the appropriate 214 // position 215 String[] currentNames = parentStructure.getNames(); 216 if (index < currentNames.length && currentNames[index].startsWith(myDirection)) { 217 newSegmentName = currentNames[index]; 218 } else { 219 try { 220 newSegmentName = parentStructure.addNonstandardSegment(myDirection, index); 221 } catch (HL7Exception e) { 222 throw new Error("Unable to add nonstandard segment " + myDirection + ": ", e); 223 } 224 } 225 226 IStructureDefinition previousSibling = getCurrentPosition().getStructureDefinition(); 227 IStructureDefinition parentStructureDefinition = parentDefinitionPath.get(parentDefinitionPath.size() - 1).getStructureDefinition(); 228 NonStandardStructureDefinition nextDefinition = new NonStandardStructureDefinition(parentStructureDefinition, previousSibling, newSegmentName, index); 229 myCurrentDefinitionPath = parentDefinitionPath; 230 myCurrentDefinitionPath.add(new Position(nextDefinition, 0)); 231 232 myNextIsSet = true; 233 } 234 235 /** 236 * <p> 237 * Returns the next node in the message. Sometimes the next node is 238 * ambiguous. For example at the end of a repeating group, the next node may 239 * be the first segment in the next repetition of the group, or the next 240 * sibling, or an undeclared segment locally added to the group's end. Cases 241 * like this are disambiguated using getDirection(), which returns the name 242 * of the structure that we are "iterating towards". Usually we are 243 * "iterating towards" a segment of a certain name because we have a segment 244 * string that we would like to parse into that node. Here are the rules: 245 * </p> 246 * <ol> 247 * <li>If at a group, next means first child.</li> 248 * <li>If at a non-repeating segment, next means next "position"</li> 249 * <li>If at a repeating segment: if segment name matches direction then 250 * next means next rep, otherwise next means next "position".</li> 251 * <li>If at a segment within a group (not at the end of the group), next 252 * "position" means next sibling</li> 253 * <li>If at the end of a group: If name of group or any of its "first 254 * decendents" matches direction, then next position means next rep of 255 * group. Otherwise if direction matches name of next sibling of the group, 256 * or any of its first descendents, next position means next sibling of the 257 * group. Otherwise, next means a new segment added to the group (with a 258 * name that matches "direction").</li> 259 * <li>"First descendents" means first child, or first child of the first 260 * child, or first child of the first child of the first child, etc.</li> 261 * </ol> 262 */ 263 @Override 264 public Structure next() { 265 if (!hasNext()) { 266 throw new NoSuchElementException("No more nodes in message"); 267 } 268 269 Structure currentStructure = navigateToStructure(myCurrentDefinitionPath); 270 271 clearNext(); 272 return currentStructure; 273 } 274 275 private Structure navigateToStructure(List<Position> theDefinitionPath) throws Error { 276 Structure currentStructure = null; 277 for (Position next : theDefinitionPath) { 278 if (currentStructure == null) { 279 currentStructure = myMessage; 280 } else { 281 try { 282 IStructureDefinition structureDefinition = next.getStructureDefinition(); 283 Group currentStructureGroup = (Group) currentStructure; 284 String nextStructureName = structureDefinition.getNameAsItAppearsInParent(); 285 currentStructure = currentStructureGroup.get(nextStructureName, next.getRepNumber()); 286 } catch (HL7Exception e) { 287 throw new Error("Failed to retrieve structure: ", e); 288 } 289 } 290 } 291 return currentStructure; 292 } 293 294 /** Not supported */ 295 @Override 296 public void remove() { 297 throw new UnsupportedOperationException("Can't remove a node from a message"); 298 } 299 300 public String getDirection() { 301 return this.myDirection; 302 } 303 304 public void setDirection(String direction) { 305 clearNext(); 306 this.myDirection = direction; 307 } 308 309 private void clearNext() { 310 myNextIsSet = false; 311 } 312 313 /** 314 * A structure position within a message. 315 */ 316 public static class Position { 317 private IStructureDefinition myStructureDefinition; 318 private int myRepNumber; 319 320 public IStructureDefinition getStructureDefinition() { 321 return myStructureDefinition; 322 } 323 324 public void resetRepNumber() { 325 myRepNumber = -1; 326 } 327 328 public void setStructureDefinition(IStructureDefinition theStructureDefinition) { 329 myStructureDefinition = theStructureDefinition; 330 } 331 332 public int getRepNumber() { 333 return myRepNumber; 334 } 335 336 public Position(IStructureDefinition theStructureDefinition, int theRepNumber) { 337 myStructureDefinition = theStructureDefinition; 338 myRepNumber = theRepNumber; 339 } 340 341 public void incrementRep() { 342 myRepNumber++; 343 } 344 345 /** @see Object#equals */ 346 public boolean equals(Object o) { 347 boolean equals = false; 348 if (o instanceof Position) { 349 Position p = (Position) o; 350 if (p.myStructureDefinition.equals(myStructureDefinition) && p.myRepNumber == myRepNumber) 351 equals = true; 352 } 353 return equals; 354 } 355 356 /** @see Object#hashCode */ 357 public int hashCode() { 358 return myStructureDefinition.hashCode() + myRepNumber; 359 } 360 361 public String toString() { 362 StringBuilder ret = new StringBuilder(); 363 364 if (myStructureDefinition.getParent() != null) { 365 ret.append(myStructureDefinition.getParent().getName()); 366 } else { 367 ret.append("Root"); 368 } 369 370 ret.append(":"); 371 ret.append(myStructureDefinition.getName()); 372 ret.append("("); 373 ret.append(myRepNumber); 374 ret.append(")"); 375 return ret.toString(); 376 } 377 } 378 379 /** 380 * Must be called after {@link #next()} 381 */ 382 public int getNextIndexWithinParent() { 383 return getCurrentPosition().getStructureDefinition().getPosition(); 384 } 385}