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 Message myMessage; 034 private String myDirection; 035 private boolean myNextIsSet; 036 private boolean myHandleUnexpectedSegments; 037 private List<Position> myCurrentDefinitionPath = new ArrayList<Position>(); 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<Position>(theDefinitionPath.subList(0, theDefinitionPath.size() - 1)); 067 068 Position newCurrentPosition = getTail(theDefinitionPath); 069 IStructureDefinition newCurrentStructureDefinition = newCurrentPosition.getStructureDefinition(); 070 071 if (newCurrentStructureDefinition.getAllPossibleFirstChildren().contains(myDirection)) { 072 return theDefinitionPath; 073 } 074 075 if (newCurrentStructureDefinition.isFinalChildOfParent()) { 076 if (theDefinitionPath.size() > 1) { 077 return popUntilMatchFound(theDefinitionPath); // recurse 078 } else { 079 log.debug("Popped to root of message and did not find a match for {}", myDirection); 080 return null; 081 } 082 } 083 084 return theDefinitionPath; 085 } 086 087 /** 088 * Returns true if another object exists in the iteration sequence. 089 */ 090 public boolean hasNext() { 091 092 log.trace("hasNext() for direction {}", myDirection); 093 if (myDirection == null) { 094 throw new IllegalStateException("Direction not set"); 095 } 096 097 while (!myNextIsSet) { 098 099 Position currentPosition = getCurrentPosition(); 100 101 log.trace("hasNext() current position: {}", currentPosition); 102 103 IStructureDefinition structureDefinition = currentPosition.getStructureDefinition(); 104 105 if (myMessage.getParser().getParserConfiguration().isNonGreedyMode()) { 106 IStructureDefinition nonGreedyPosition = couldBeNotGreedy(); 107 if (nonGreedyPosition != null) { 108 log.info("Found non greedy parsing choice, moving to {}", nonGreedyPosition.getName()); 109 while (getCurrentPosition().getStructureDefinition() != nonGreedyPosition) { 110 myCurrentDefinitionPath.remove(myCurrentDefinitionPath.size() - 1); 111 } 112 } 113 } 114 115 if (structureDefinition.isSegment() && structureDefinition.getName().startsWith(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) { 116 myNextIsSet = true; 117 currentPosition.incrementRep(); 118 } else if (structureDefinition.isSegment() && structureDefinition.getNextLeaf() == null 119 && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) { 120 if (!myHandleUnexpectedSegments) { 121 return false; 122 } 123 addNonStandardSegmentAtCurrentPosition(); 124 } else if (structureDefinition.hasChildren() && structureDefinition.getAllPossibleFirstChildren().contains(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) { 125 currentPosition.incrementRep(); 126 myCurrentDefinitionPath.add(new Position(structureDefinition.getFirstChild(), -1)); 127 } else if (!structureDefinition.hasChildren() && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) { 128 if (!myHandleUnexpectedSegments) { 129 return false; 130 } 131 addNonStandardSegmentAtCurrentPosition(); 132 // } else if (structureDefinition.isMessage()) { 133 // if (!handleUnexpectedSegments) { 134 // return false; 135 // } 136 // addNonStandardSegmentAtCurrentPosition(); 137 } else if (structureDefinition.isFinalChildOfParent()) { 138 List<Position> newDefinitionPath = popUntilMatchFound(myCurrentDefinitionPath); 139 if (newDefinitionPath != null) { 140 // found match 141 myCurrentDefinitionPath = newDefinitionPath; 142 } else { 143 if (!myHandleUnexpectedSegments) { 144 return false; 145 } 146 addNonStandardSegmentAtCurrentPosition(); 147 } 148 } else { 149 currentPosition.setStructureDefinition(structureDefinition.getNextSibling()); 150 currentPosition.resetRepNumber(); 151 } 152 153 } 154 155 return true; 156 } 157 158 /** 159 * @see ParserConfiguration#setNonGreedyMode(boolean) 160 */ 161 private IStructureDefinition couldBeNotGreedy() { 162 for (int i = myCurrentDefinitionPath.size() - 1; i >= 1; i--) { 163 Position position = myCurrentDefinitionPath.get(i); 164 IStructureDefinition curPos = position.getStructureDefinition(); 165 if (curPos.getPosition() > 0) { 166 IStructureDefinition parent = curPos.getParent(); 167 if (parent.isRepeating() && parent.getAllPossibleFirstChildren().contains(myDirection)) { 168 return parent; 169 } 170 } 171 172 } 173 174 return null; 175 } 176 177 private void addNonStandardSegmentAtCurrentPosition() throws Error { 178 log.debug("Creating non standard segment {} on group: {}", 179 myDirection, getCurrentPosition().getStructureDefinition().getParent().getName()); 180 181 List<Position> parentDefinitionPath; 182 Group parentStructure; 183 184 switch (myMessage.getParser().getParserConfiguration().getUnexpectedSegmentBehaviour()) { 185 case ADD_INLINE: 186 default: 187 parentDefinitionPath = new ArrayList<Position>(myCurrentDefinitionPath.subList(0, myCurrentDefinitionPath.size() - 1)); 188 parentStructure = (Group) navigateToStructure(parentDefinitionPath); 189 break; 190 case DROP_TO_ROOT: 191 parentDefinitionPath = new ArrayList<Position>(myCurrentDefinitionPath.subList(0, 1)); 192 parentStructure = myMessage; 193 myCurrentDefinitionPath = myCurrentDefinitionPath.subList(0, 2); 194 break; 195 case THROW_HL7_EXCEPTION: 196 throw new Error(new HL7Exception("Found unknown segment: " + myDirection)); 197 } 198 199 200 // Current position within parent 201 Position currentPosition = getCurrentPosition(); 202 String nameAsItAppearsInParent = currentPosition.getStructureDefinition().getNameAsItAppearsInParent(); 203 204 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 String[] currentNames = parentStructure.getNames(); 211 if (index < currentNames.length && currentNames[index].startsWith(myDirection)) { 212 newSegmentName = currentNames[index]; 213 } else { 214 try { 215 newSegmentName = parentStructure.addNonstandardSegment(myDirection, index); 216 } catch (HL7Exception e) { 217 throw new Error("Unable to add nonstandard segment " + myDirection + ": ", e); 218 } 219 } 220 221 IStructureDefinition previousSibling = getCurrentPosition().getStructureDefinition(); 222 IStructureDefinition parentStructureDefinition = parentDefinitionPath.get(parentDefinitionPath.size() - 1).getStructureDefinition(); 223 NonStandardStructureDefinition nextDefinition = new NonStandardStructureDefinition(parentStructureDefinition, previousSibling, newSegmentName, index); 224 myCurrentDefinitionPath = parentDefinitionPath; 225 myCurrentDefinitionPath.add(new Position(nextDefinition, 0)); 226 227 myNextIsSet = true; 228 } 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 if (!hasNext()) { 260 throw new NoSuchElementException("No more nodes in message"); 261 } 262 263 Structure currentStructure = navigateToStructure(myCurrentDefinitionPath); 264 265 clearNext(); 266 return currentStructure; 267 } 268 269 private Structure navigateToStructure(List<Position> theDefinitionPath) throws Error { 270 Structure currentStructure = null; 271 for (Position next : theDefinitionPath) { 272 if (currentStructure == null) { 273 currentStructure = myMessage; 274 } else { 275 try { 276 IStructureDefinition structureDefinition = next.getStructureDefinition(); 277 Group currentStructureGroup = (Group) currentStructure; 278 String nextStructureName = structureDefinition.getNameAsItAppearsInParent(); 279 currentStructure = currentStructureGroup.get(nextStructureName, next.getRepNumber()); 280 } catch (HL7Exception e) { 281 throw new Error("Failed to retrieve structure: ", e); 282 } 283 } 284 } 285 return currentStructure; 286 } 287 288 /** Not supported */ 289 public void remove() { 290 throw new UnsupportedOperationException("Can't remove a node from a message"); 291 } 292 293 public String getDirection() { 294 return this.myDirection; 295 } 296 297 public void setDirection(String direction) { 298 clearNext(); 299 this.myDirection = direction; 300 } 301 302 private void clearNext() { 303 myNextIsSet = false; 304 } 305 306 /** 307 * A structure position within a message. 308 */ 309 public static class Position { 310 private IStructureDefinition myStructureDefinition; 311 private int myRepNumber = -1; 312 313 public IStructureDefinition getStructureDefinition() { 314 return myStructureDefinition; 315 } 316 317 public void resetRepNumber() { 318 myRepNumber = -1; 319 } 320 321 public void setStructureDefinition(IStructureDefinition theStructureDefinition) { 322 myStructureDefinition = theStructureDefinition; 323 } 324 325 public int getRepNumber() { 326 return myRepNumber; 327 } 328 329 public Position(IStructureDefinition theStructureDefinition, int theRepNumber) { 330 myStructureDefinition = theStructureDefinition; 331 myRepNumber = theRepNumber; 332 } 333 334 public void incrementRep() { 335 myRepNumber++; 336 } 337 338 /** @see Object#equals */ 339 public boolean equals(Object o) { 340 boolean equals = false; 341 if (o != null && o instanceof Position) { 342 Position p = (Position) o; 343 if (p.myStructureDefinition.equals(myStructureDefinition) && p.myRepNumber == myRepNumber) 344 equals = true; 345 } 346 return equals; 347 } 348 349 /** @see Object#hashCode */ 350 public int hashCode() { 351 return myStructureDefinition.hashCode() + myRepNumber; 352 } 353 354 public String toString() { 355 StringBuilder ret = new StringBuilder(); 356 357 if (myStructureDefinition.getParent() != null) { 358 ret.append(myStructureDefinition.getParent().getName()); 359 } else { 360 ret.append("Root"); 361 } 362 363 ret.append(":"); 364 ret.append(myStructureDefinition.getName()); 365 ret.append("("); 366 ret.append(myRepNumber); 367 ret.append(")"); 368 return ret.toString(); 369 } 370 } 371 372 /** 373 * Must be called after {@link #next()} 374 */ 375 public int getNextIndexWithinParent() { 376 return getCurrentPosition().getStructureDefinition().getPosition(); 377 } 378}