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.debug("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.debug("hasNext() current position: {}", currentPosition); 102 103 IStructureDefinition structureDefinition = currentPosition.getStructureDefinition(); 104 if (structureDefinition.isSegment() && structureDefinition.getName().startsWith(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) { 105 myNextIsSet = true; 106 currentPosition.incrementRep(); 107 } else if (structureDefinition.isSegment() && structureDefinition.getNextLeaf() == null 108 && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) { 109 if (!myHandleUnexpectedSegments) { 110 return false; 111 } 112 addNonStandardSegmentAtCurrentPosition(); 113 } else if (structureDefinition.hasChildren() && structureDefinition.getAllPossibleFirstChildren().contains(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) { 114 currentPosition.incrementRep(); 115 myCurrentDefinitionPath.add(new Position(structureDefinition.getFirstChild(), -1)); 116 } else if (!structureDefinition.hasChildren() && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) { 117 if (!myHandleUnexpectedSegments) { 118 return false; 119 } 120 addNonStandardSegmentAtCurrentPosition(); 121 // } else if (structureDefinition.isMessage()) { 122 // if (!handleUnexpectedSegments) { 123 // return false; 124 // } 125 // addNonStandardSegmentAtCurrentPosition(); 126 } else if (structureDefinition.isFinalChildOfParent()) { 127 List<Position> newDefinitionPath = popUntilMatchFound(myCurrentDefinitionPath); 128 if (newDefinitionPath != null) { 129 // found match 130 myCurrentDefinitionPath = newDefinitionPath; 131 } else { 132 if (!myHandleUnexpectedSegments) { 133 return false; 134 } 135 addNonStandardSegmentAtCurrentPosition(); 136 } 137 } else { 138 currentPosition.setStructureDefinition(structureDefinition.getNextSibling()); 139 currentPosition.resetRepNumber(); 140 } 141 142 } 143 144 return true; 145 } 146 147 private void addNonStandardSegmentAtCurrentPosition() throws Error { 148 log.debug("Creating non standard segment {} on group: {}", 149 myDirection, getCurrentPosition().getStructureDefinition().getParent().getName()); 150 List<Position> parentDefinitionPath = new ArrayList<Position>(myCurrentDefinitionPath.subList(0, myCurrentDefinitionPath.size() - 1)); 151 Group parentStructure = (Group) navigateToStructure(parentDefinitionPath); 152 153 // Current position within parent 154 Position currentPosition = getCurrentPosition(); 155 String nameAsItAppearsInParent = currentPosition.getStructureDefinition().getNameAsItAppearsInParent(); 156 157 int index = Arrays.asList(parentStructure.getNames()).indexOf(nameAsItAppearsInParent) + 1; 158 159 String newSegmentName; 160 161 // Check if the structure already has a non-standard segment in the appropriate 162 // position 163 String[] currentNames = parentStructure.getNames(); 164 if (index < currentNames.length && currentNames[index].startsWith(myDirection)) { 165 newSegmentName = currentNames[index]; 166 } else { 167 try { 168 newSegmentName = parentStructure.addNonstandardSegment(myDirection, index); 169 } catch (HL7Exception e) { 170 throw new Error("Unable to add nonstandard segment " + myDirection + ": ", e); 171 } 172 } 173 174 IStructureDefinition previousSibling = getCurrentPosition().getStructureDefinition(); 175 IStructureDefinition parentStructureDefinition = parentDefinitionPath.get(parentDefinitionPath.size() - 1).getStructureDefinition(); 176 NonStandardStructureDefinition nextDefinition = new NonStandardStructureDefinition(parentStructureDefinition, previousSibling, newSegmentName, index); 177 myCurrentDefinitionPath = parentDefinitionPath; 178 myCurrentDefinitionPath.add(new Position(nextDefinition, 0)); 179 180 myNextIsSet = true; 181 } 182 183 /** 184 * <p> 185 * Returns the next node in the message. Sometimes the next node is 186 * ambiguous. For example at the end of a repeating group, the next node may 187 * be the first segment in the next repetition of the group, or the next 188 * sibling, or an undeclared segment locally added to the group's end. Cases 189 * like this are disambiguated using getDirection(), which returns the name 190 * of the structure that we are "iterating towards". Usually we are 191 * "iterating towards" a segment of a certain name because we have a segment 192 * string that we would like to parse into that node. Here are the rules: 193 * </p> 194 * <ol> 195 * <li>If at a group, next means first child.</li> 196 * <li>If at a non-repeating segment, next means next "position"</li> 197 * <li>If at a repeating segment: if segment name matches direction then 198 * next means next rep, otherwise next means next "position".</li> 199 * <li>If at a segment within a group (not at the end of the group), next 200 * "position" means next sibling</li> 201 * <li>If at the end of a group: If name of group or any of its "first 202 * decendents" matches direction, then next position means next rep of 203 * group. Otherwise if direction matches name of next sibling of the group, 204 * or any of its first descendents, next position means next sibling of the 205 * group. Otherwise, next means a new segment added to the group (with a 206 * name that matches "direction").</li> 207 * <li>"First descendents" means first child, or first child of the first 208 * child, or first child of the first child of the first child, etc.</li> 209 * </ol> 210 */ 211 public Structure next() { 212 if (!hasNext()) { 213 throw new NoSuchElementException("No more nodes in message"); 214 } 215 216 Structure currentStructure = navigateToStructure(myCurrentDefinitionPath); 217 218 clearNext(); 219 return currentStructure; 220 } 221 222 private Structure navigateToStructure(List<Position> theDefinitionPath) throws Error { 223 Structure currentStructure = null; 224 for (Position next : theDefinitionPath) { 225 if (currentStructure == null) { 226 currentStructure = myMessage; 227 } else { 228 try { 229 IStructureDefinition structureDefinition = next.getStructureDefinition(); 230 Group currentStructureGroup = (Group) currentStructure; 231 String nextStructureName = structureDefinition.getNameAsItAppearsInParent(); 232 currentStructure = currentStructureGroup.get(nextStructureName, next.getRepNumber()); 233 } catch (HL7Exception e) { 234 throw new Error("Failed to retrieve structure: ", e); 235 } 236 } 237 } 238 return currentStructure; 239 } 240 241 /** Not supported */ 242 public void remove() { 243 throw new UnsupportedOperationException("Can't remove a node from a message"); 244 } 245 246 public String getDirection() { 247 return this.myDirection; 248 } 249 250 public void setDirection(String direction) { 251 clearNext(); 252 this.myDirection = direction; 253 } 254 255 private void clearNext() { 256 myNextIsSet = false; 257 } 258 259 /** 260 * A structure position within a message. 261 */ 262 public static class Position { 263 private IStructureDefinition myStructureDefinition; 264 private int myRepNumber = -1; 265 266 public IStructureDefinition getStructureDefinition() { 267 return myStructureDefinition; 268 } 269 270 public void resetRepNumber() { 271 myRepNumber = -1; 272 } 273 274 public void setStructureDefinition(IStructureDefinition theStructureDefinition) { 275 myStructureDefinition = theStructureDefinition; 276 } 277 278 public int getRepNumber() { 279 return myRepNumber; 280 } 281 282 public Position(IStructureDefinition theStructureDefinition, int theRepNumber) { 283 myStructureDefinition = theStructureDefinition; 284 myRepNumber = theRepNumber; 285 } 286 287 public void incrementRep() { 288 myRepNumber++; 289 } 290 291 /** @see Object#equals */ 292 public boolean equals(Object o) { 293 boolean equals = false; 294 if (o != null && o instanceof Position) { 295 Position p = (Position) o; 296 if (p.myStructureDefinition.equals(myStructureDefinition) && p.myRepNumber == myRepNumber) 297 equals = true; 298 } 299 return equals; 300 } 301 302 /** @see Object#hashCode */ 303 public int hashCode() { 304 return myStructureDefinition.hashCode() + myRepNumber; 305 } 306 307 public String toString() { 308 StringBuffer ret = new StringBuffer(); 309 310 if (myStructureDefinition.getParent() != null) { 311 ret.append(myStructureDefinition.getParent().getName()); 312 } else { 313 ret.append("Root"); 314 } 315 316 ret.append(":"); 317 ret.append(myStructureDefinition.getName()); 318 ret.append("("); 319 ret.append(myRepNumber); 320 ret.append(")"); 321 return ret.toString(); 322 } 323 } 324 325 /** 326 * Must be called after {@link #next()} 327 * 328 * @return 329 */ 330 public int getNextIndexWithinParent() { 331 return getCurrentPosition().getStructureDefinition().getPosition(); 332 } 333}