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