001 package ca.uhn.hl7v2.util; 002 003 import ca.uhn.hl7v2.model.*; 004 import ca.uhn.log.*; 005 import ca.uhn.hl7v2.HL7Exception; 006 import java.util.*; 007 008 /** 009 * Iterates over all defined nodes (ie segments, groups) in a message, 010 * regardless of whether they have been instantiated previously. This is a 011 * tricky process, because the number of nodes is infinite, due to infinitely 012 * repeating segments and groups. See <code>next()</code> for details on 013 * how this is handled. 014 * 015 * This implementation assumes that the first segment in each group is present (as per 016 * HL7 rules). Specifically, when looking for a segment location, an empty group that has 017 * a spot for the segment will be overlooked if there is anything else before that spot. 018 * This may result in surprising (but sensible) behaviour if a message is missing the 019 * first segment in a group. 020 * 021 * @author Bryan Tripp 022 */ 023 public class MessageIterator implements java.util.Iterator { 024 025 private Structure currentStructure; 026 private String direction; 027 private Position next; 028 private boolean handleUnexpectedSegments; 029 030 private static final HapiLog log = HapiLogFactory.getHapiLog(MessageIterator.class); 031 032 /* may add configurability later ... 033 private boolean findUpToFirstRequired; 034 private boolean findFirstDescendentsOnly; 035 036 public static final String WHOLE_GROUP; 037 public static final String FIRST_DESCENDENTS_ONLY; 038 public static final String UP_TO_FIRST_REQUIRED; 039 */ 040 041 /** Creates a new instance of MessageIterator */ 042 public MessageIterator(Structure start, String direction, boolean handleUnexpectedSegments) { 043 this.currentStructure = start; 044 this.direction = direction; 045 this.handleUnexpectedSegments = handleUnexpectedSegments; 046 } 047 048 /* for configurability (maybe to add later, replacing hard-coded options 049 in nextFromEndOfGroup) ... 050 public void setSearchLevel(String level) { 051 if (WHOLE_GROUP.equals(level)) { 052 this.findUpToFirstRequired = false; 053 this.findFirstDescendentsOnly = false; 054 } else if (FIRST_DESCENDENTS_ONLY.equals(level)) { 055 this.findUpToFirstRequired = false; 056 this.findFirstDescendentsOnly = true; 057 } else if (UP_TO_FIRST_REQUIRED.equals(level)) { 058 this.findUpToFirstRequired = true; 059 this.findFirstDescendentsOnly = false; 060 } else { 061 throw IllegalArgumentException(level + " is not a valid search level. Should be WHOLE_GROUP, etc."); 062 } 063 } 064 065 public String getSearchLevel() { 066 String level = WHOLE_GROUP; 067 if (this.findFirstDescendentsOnly) { 068 level = FIRST_DESCENDENTS_ONLY; 069 } else if (this.findUpTpFirstRequired) { 070 level = UP_TO_FIRST_REQUIRED; 071 } 072 return level; 073 }*/ 074 075 076 /** 077 * Returns true if another object exists in the iteration sequence. 078 */ 079 public boolean hasNext() { 080 boolean has = true; 081 if (next == null) { 082 if (Group.class.isAssignableFrom(currentStructure.getClass())) { 083 groupNext((Group) currentStructure); 084 } else { 085 Group parent = currentStructure.getParent(); 086 Index i = getIndex(parent, currentStructure); 087 Position currentPosition = new Position(parent, i); 088 089 try { 090 if (parent.isRepeating(i.name) && currentStructure.getName().equals(direction)) { 091 nextRep(currentPosition); 092 } else { 093 has = nextPosition(currentPosition, this.direction, this.handleUnexpectedSegments); 094 } 095 } catch (HL7Exception e) { 096 throw new Error("HL7Exception arising from bad index: " + e.getMessage()); 097 } 098 } 099 } 100 log.debug("MessageIterator.hasNext() in direction " + this.direction + "? " + has); 101 return has; 102 } 103 104 /** 105 * Sets next to the first child of the given group (iteration 106 * always proceeds from group to first child). 107 */ 108 private void groupNext(Group current) { 109 next = new Position(current, ((Group) current).getNames()[0], 0); 110 } 111 112 /** 113 * Sets next to the next repetition of the current structure. 114 */ 115 private void nextRep(Position current) { 116 next = new Position(current.parent, current.index.name, current.index.rep + 1); 117 } 118 119 /** 120 * Sets this.next to the next position in the message (from the given position), 121 * which could be the next sibling, a new segment, or the next rep 122 * of the parent. See next() for details. 123 */ 124 private boolean nextPosition(Position currPos, String direction, boolean makeNewSegmentIfNeeded) throws HL7Exception { 125 boolean nextExists = true; 126 if (isLast(currPos)) { 127 nextExists = nextFromGroupEnd(currPos, direction, makeNewSegmentIfNeeded); 128 } else { 129 nextSibling(currPos); 130 } 131 return nextExists; 132 } 133 134 /** Navigates from end of group */ 135 private boolean nextFromGroupEnd(Position currPos, String direction, boolean makeNewSegmentIfNeeded) throws HL7Exception { 136 assert isLast(currPos); 137 boolean nextExists = true; 138 139 //the following conditional logic is a little convoluted -- its meant as an optimization 140 // i.e. trying to avoid calling matchExistsAfterCurrentPosition 141 142 if (!makeNewSegmentIfNeeded && Message.class.isAssignableFrom(currPos.parent.getClass())) { 143 nextExists = false; 144 } else if (!makeNewSegmentIfNeeded || matchExistsAfterPosition(currPos, direction, false, true)) { 145 Group grandparent = currPos.parent.getParent(); 146 Index parentIndex = getIndex(grandparent, currPos.parent); 147 Position parentPos = new Position(grandparent, parentIndex); 148 149 try { 150 boolean parentRepeats = parentPos.parent.isRepeating(parentPos.index.name); 151 if (parentRepeats && contains(parentPos.parent.get(parentPos.index.name, 0), direction, false, true)) { 152 nextRep(parentPos); 153 } else { 154 nextExists = nextPosition(parentPos, direction, makeNewSegmentIfNeeded); 155 } 156 } catch (HL7Exception e) { 157 throw new Error("HL7Exception arising from bad index: " + e.getMessage()); 158 } 159 } else { 160 newSegment(currPos.parent, direction); 161 } 162 return nextExists; 163 } 164 165 /** 166 * A match exists for the given name somewhere after the given position (in the 167 * normal serialization order). 168 * @param pos the message position after which to look (note that this specifies 169 * the message instance) 170 * @param name the name of the structure to look for 171 * @param firstDescendentsOnly only searches the first children of a group 172 * @param upToFirstRequired only searches the children of a group up to the first 173 * required child (normally the first one). This is used when we are parsing 174 * a message in order and looking for a place to parse a particular segment -- 175 * if the message is correct then it can't go after a required position of a 176 * different name. 177 */ 178 public static boolean matchExistsAfterPosition(Position pos, String name, boolean firstDescendentsOnly, boolean upToFirstRequired) throws HL7Exception { 179 boolean matchExists = false; 180 181 //check next rep of self (if any) 182 if (pos.parent.isRepeating(pos.index.name)) { 183 Structure s = pos.parent.get(pos.index.name, pos.index.rep); 184 matchExists = contains(s, name, firstDescendentsOnly, upToFirstRequired); 185 } 186 187 //check later siblings (if any) 188 if (!matchExists) { 189 String[] siblings = pos.parent.getNames(); 190 boolean after = false; 191 for (int i = 0; i < siblings.length && !matchExists; i++) { 192 if (after) { 193 matchExists = contains(pos.parent.get(siblings[i]), name, firstDescendentsOnly, upToFirstRequired); 194 if (upToFirstRequired && pos.parent.isRequired(siblings[i])) break; 195 } 196 if (pos.index.name.equals(siblings[i])) after = true; 197 } 198 } 199 200 //recurse to parent (if parent is not message root) 201 if (!matchExists && !Message.class.isAssignableFrom(pos.parent.getClass())) { 202 Group grandparent = pos.parent.getParent(); 203 Position parentPos = new Position(grandparent, getIndex(grandparent, pos.parent)); 204 matchExists = matchExistsAfterPosition(parentPos, name, firstDescendentsOnly, upToFirstRequired); 205 } 206 log.debug("Match exists after position " + pos + " for " + name + "? " + matchExists); 207 return matchExists; 208 } 209 210 /** 211 * Sets the next position to a new segment of the given name, within the 212 * given group. 213 */ 214 private void newSegment(Group parent, String name) throws HL7Exception { 215 log.info("MessageIterator creating new segment: " + name); 216 parent.addNonstandardSegment(name); 217 next = new Position(parent, parent.getNames()[parent.getNames().length-1], 0); 218 } 219 220 /** 221 * Determines whether the given structure matches the given name, or contains 222 * a child that does. 223 * @param s the structure to check 224 * @param name the name to look for 225 * @param firstDescendentsOnly only checks first descendents (i.e. first 226 * child, first child of first child, etc.) In theory the first child 227 * of a group should always be present, and we don't use this method with 228 * subsequent children because finding the next position within a group is 229 * straightforward. 230 * @param upToFirstRequired only checks first descendents and of their siblings 231 * up to the first required one. This may be needed because in practice 232 * some first children of groups are not required. 233 */ 234 public static boolean contains(Structure s, String name, boolean firstDescendentsOnly, boolean upToFirstRequired) { 235 boolean contains = false; 236 if (Segment.class.isAssignableFrom(s.getClass())) { 237 if (s.getName().equals(name)) contains = true; 238 } else { 239 Group g = (Group) s; 240 String[] names = g.getNames(); 241 for (int i = 0; i < names.length && !contains; i++) { 242 try { 243 contains = contains(g.get(names[i], 0), name, firstDescendentsOnly, upToFirstRequired); 244 if (firstDescendentsOnly) break; 245 if (upToFirstRequired && g.isRequired(names[i])) break; 246 } catch (HL7Exception e) { 247 throw new Error("HL7Exception due to bad index: " + e.getMessage()); 248 } 249 } 250 } 251 return contains; 252 } 253 254 /** 255 * Tests whether the name of the given Index matches 256 * the name of the last child of the given group. 257 */ 258 public static boolean isLast(Position p) { 259 String[] names = p.parent.getNames(); 260 return names[names.length-1].equals(p.index.name); 261 } 262 263 /** 264 * Sets the next location to the next sibling of the given 265 * index. 266 */ 267 private void nextSibling(Position pos) { 268 String[] names = pos.parent.getNames(); 269 int i = 0; 270 for (; i < names.length && !names[i].equals(pos.index.name); i++) {} 271 String nextName = names[i+1]; 272 273 this.next = new Position(pos.parent, nextName, 0); 274 } 275 276 /** 277 * <p>Returns the next node in the message. Sometimes the next node is 278 * ambiguous. For example at the end of a repeating group, the next node 279 * may be the first segment in the next repetition of the group, or the 280 * next sibling, or an undeclared segment locally added to the group's end. 281 * Cases like this are disambiguated using getDirection(), which returns 282 * the name of the structure that we are "iterating towards". 283 * Usually we are "iterating towards" a segment of a certain name because we 284 * have a segment string that we would like to parse into that node. 285 * Here are the rules: </p> 286 * <ol><li>If at a group, next means first child.</li> 287 * <li>If at a non-repeating segment, next means next "position"</li> 288 * <li>If at a repeating segment: if segment name matches 289 * direction then next means next rep, otherwise next means next "position".</li> 290 * <li>If at a segment within a group (not at the end of the group), next "position" 291 * means next sibling</li> 292 * <li>If at the end of a group: If name of group or any of its "first 293 * decendents" matches direction, then next position means next rep of group. Otherwise 294 * if direction matches name of next sibling of the group, or any of its first 295 * descendents, next position means next sibling of the group. Otherwise, next means a 296 * new segment added to the group (with a name that matches "direction"). </li> 297 * <li>"First descendents" means first child, or first child of the first child, 298 * or first child of the first child of the first child, etc. </li> </ol> 299 */ 300 public Object next() { 301 if (!hasNext()) { 302 throw new NoSuchElementException("No more nodes in message"); 303 } 304 try { 305 this.currentStructure = next.parent.get(next.index.name, next.index.rep); 306 } catch (HL7Exception e) { 307 throw new NoSuchElementException("HL7Exception: " + e.getMessage()); 308 } 309 clearNext(); 310 return this.currentStructure; 311 } 312 313 /** Not supported */ 314 public void remove() { 315 throw new UnsupportedOperationException("Can't remove a node from a message"); 316 } 317 318 public String getDirection() { 319 return this.direction; 320 } 321 322 public void setDirection(String direction) { 323 clearNext(); 324 this.direction = direction; 325 } 326 327 private void clearNext() { 328 next = null; 329 } 330 331 /** 332 * Returns the index of the given structure as a child of the 333 * given parent. Returns null if the child isn't found. 334 */ 335 public static Index getIndex(Group parent, Structure child) { 336 Index index = null; 337 String[] names = parent.getNames(); 338 findChild : for (int i = 0; i < names.length; i++) { 339 if (names[i].startsWith(child.getName())) { 340 try { 341 Structure[] reps = parent.getAll(names[i]); 342 for (int j = 0; j < reps.length; j++) { 343 if (child == reps[j]) { 344 index = new Index(names[i], j); 345 break findChild; 346 } 347 } 348 } catch (HL7Exception e) { 349 log.error("", e); 350 throw new Error("Internal HL7Exception finding structure index: " + e.getMessage()); 351 } 352 } 353 } 354 return index; 355 } 356 357 /** 358 * An index of a child structure within a group, consisting of the name and rep of 359 * of the child. 360 */ 361 public static class Index { 362 public String name; 363 public int rep; 364 public Index(String name, int rep) { 365 this.name = name; 366 this.rep = rep; 367 } 368 369 /** @see Object#equals */ 370 public boolean equals(Object o) { 371 boolean equals = false; 372 if (o != null && o instanceof Index) { 373 Index i = (Index) o; 374 if (i.rep == rep && i.name.equals(name)) equals = true; 375 } 376 return equals; 377 } 378 379 /** @see Object#hashCode */ 380 public int hashCode() { 381 return name.hashCode() + 700 * rep; 382 } 383 384 /** @see Object#toString */ 385 public String toString() { 386 return this.name + ":" + this.rep; 387 } 388 } 389 390 /** 391 * A structure position within a message. 392 */ 393 public static class Position { 394 public Group parent; 395 public Index index; 396 public Position(Group parent, String name, int rep) { 397 this.parent = parent; 398 this.index = new Index(name, rep); 399 } 400 public Position(Group parent, Index i) { 401 this.parent = parent; 402 this.index = i; 403 } 404 405 /** @see Object#equals */ 406 public boolean equals(Object o) { 407 boolean equals = false; 408 if (o != null && o instanceof Position) { 409 Position p = (Position) o; 410 if (p.parent.equals(parent) && p.index.equals(index)) equals = true; 411 } 412 return equals; 413 } 414 415 /** @see Object#hashCode */ 416 public int hashCode() { 417 return parent.hashCode() + index.hashCode(); 418 } 419 420 public String toString() { 421 StringBuffer ret = new StringBuffer(parent.getName()); 422 ret.append(":"); 423 ret.append(index.name); 424 ret.append("("); 425 ret.append(index.rep); 426 ret.append(")"); 427 return ret.toString(); 428 } 429 } 430 }