1 package ca.uhn.hl7v2.util;
2
3 import java.util.NoSuchElementException;
4
5 import org.slf4j.Logger;
6 import org.slf4j.LoggerFactory;
7
8 import ca.uhn.hl7v2.HL7Exception;
9 import ca.uhn.hl7v2.model.Group;
10 import ca.uhn.hl7v2.model.Message;
11 import ca.uhn.hl7v2.model.Segment;
12 import ca.uhn.hl7v2.model.Structure;
13
14 /**
15 * Iterates over all defined nodes (ie segments, groups) in a message,
16 * regardless of whether they have been instantiated previously. This is a
17 * tricky process, because the number of nodes is infinite, due to infinitely
18 * repeating segments and groups. See <code>next()</code> for details on
19 * how this is handled.
20 *
21 * This implementation assumes that the first segment in each group is present (as per
22 * HL7 rules). Specifically, when looking for a segment location, an empty group that has
23 * a spot for the segment will be overlooked if there is anything else before that spot.
24 * This may result in surprising (but sensible) behaviour if a message is missing the
25 * first segment in a group.
26 *
27 * @author Bryan Tripp
28 */
29 public class MessageIterator implements java.util.Iterator<Structure> {
30
31 private Structure currentStructure;
32 private String direction;
33 private Position next;
34 private final boolean handleUnexpectedSegments;
35
36 private static final Logger log = LoggerFactory.getLogger(MessageIterator.class);
37
38 /* may add configurability later ...
39 private boolean findUpToFirstRequired;
40 private boolean findFirstDescendentsOnly;
41
42 public static final String WHOLE_GROUP;
43 public static final String FIRST_DESCENDENTS_ONLY;
44 public static final String UP_TO_FIRST_REQUIRED;
45 */
46
47 /** Creates a new instance of MessageIterator */
48 public MessageIterator(Structure start, String direction, boolean handleUnexpectedSegments) {
49 this.currentStructure = start;
50 this.direction = direction;
51 this.handleUnexpectedSegments = handleUnexpectedSegments;
52 }
53
54 /* for configurability (maybe to add later, replacing hard-coded options
55 in nextFromEndOfGroup) ...
56 public void setSearchLevel(String level) {
57 if (WHOLE_GROUP.equals(level)) {
58 this.findUpToFirstRequired = false;
59 this.findFirstDescendentsOnly = false;
60 } else if (FIRST_DESCENDENTS_ONLY.equals(level)) {
61 this.findUpToFirstRequired = false;
62 this.findFirstDescendentsOnly = true;
63 } else if (UP_TO_FIRST_REQUIRED.equals(level)) {
64 this.findUpToFirstRequired = true;
65 this.findFirstDescendentsOnly = false;
66 } else {
67 throw IllegalArgumentException(level + " is not a valid search level. Should be WHOLE_GROUP, etc.");
68 }
69 }
70
71 public String getSearchLevel() {
72 String level = WHOLE_GROUP;
73 if (this.findFirstDescendentsOnly) {
74 level = FIRST_DESCENDENTS_ONLY;
75 } else if (this.findUpTpFirstRequired) {
76 level = UP_TO_FIRST_REQUIRED;
77 }
78 return level;
79 }*/
80
81
82 /**
83 * Returns true if another object exists in the iteration sequence.
84 */
85 public boolean hasNext() {
86 boolean has = true;
87 if (next == null) {
88 if (Group.class.isAssignableFrom(currentStructure.getClass())) {
89 groupNext((Group) currentStructure);
90 } else {
91 Group parent = currentStructure.getParent();
92 Index i = getIndex(parent, currentStructure);
93 Position currentPosition = new Position(parent, i);
94
95 try {
96 if (parent.isRepeating(i.name) && currentStructure.getName().equals(direction)) {
97 nextRep(currentPosition);
98 } else {
99 has = nextPosition(currentPosition, this.direction, this.handleUnexpectedSegments);
100 }
101 } catch (HL7Exception e) {
102 throw new Error("HL7Exception arising from bad index: " + e.getMessage());
103 }
104 }
105 }
106 log.debug("MessageIterator.hasNext() in direction {}? {}", direction, has);
107 return has;
108 }
109
110 /**
111 * Sets next to the first child of the given group (iteration
112 * always proceeds from group to first child).
113 */
114 private void groupNext(Group current) {
115 next = new Position(current, current.getNames()[0], 0);
116 }
117
118 /**
119 * Sets next to the next repetition of the current structure.
120 */
121 private void nextRep(Position current) {
122 next = new Position(current.parent, current.index.name, current.index.rep + 1);
123 }
124
125 /**
126 * Sets this.next to the next position in the message (from the given position),
127 * which could be the next sibling, a new segment, or the next rep
128 * of the parent. See next() for details.
129 */
130 private boolean nextPosition(Position currPos, String direction, boolean makeNewSegmentIfNeeded) throws HL7Exception {
131 boolean nextExists = true;
132 if (isLast(currPos)) {
133 nextExists = nextFromGroupEnd(currPos, direction, makeNewSegmentIfNeeded);
134 } else {
135 nextSibling(currPos);
136 }
137 return nextExists;
138 }
139
140 /** Navigates from end of group */
141 private boolean nextFromGroupEnd(Position currPos, String direction, boolean makeNewSegmentIfNeeded) throws HL7Exception {
142 assert isLast(currPos);
143 boolean nextExists = true;
144
145 //the following conditional logic is a little convoluted -- its meant as an optimization
146 // i.e. trying to avoid calling matchExistsAfterCurrentPosition
147
148 if (!makeNewSegmentIfNeeded && Message.class.isAssignableFrom(currPos.parent.getClass())) {
149 nextExists = false;
150 } else if (!makeNewSegmentIfNeeded || matchExistsAfterPosition(currPos, direction, false, true)) {
151 Group grandparent = currPos.parent.getParent();
152 Index parentIndex = getIndex(grandparent, currPos.parent);
153 Position parentPos = new Position(grandparent, parentIndex);
154
155 try {
156 boolean parentRepeats = parentPos.parent.isRepeating(parentPos.index.name);
157 if (parentRepeats && contains(parentPos.parent.get(parentPos.index.name, 0), direction, false, true)) {
158 nextRep(parentPos);
159 } else {
160 nextExists = nextPosition(parentPos, direction, makeNewSegmentIfNeeded);
161 }
162 } catch (HL7Exception e) {
163 throw new Error("HL7Exception arising from bad index: " + e.getMessage());
164 }
165 } else {
166 newSegment(currPos.parent, direction);
167 }
168 return nextExists;
169 }
170
171 /**
172 * A match exists for the given name somewhere after the given position (in the
173 * normal serialization order).
174 * @param pos the message position after which to look (note that this specifies
175 * the message instance)
176 * @param name the name of the structure to look for
177 * @param firstDescendentsOnly only searches the first children of a group
178 * @param upToFirstRequired only searches the children of a group up to the first
179 * required child (normally the first one). This is used when we are parsing
180 * a message in order and looking for a place to parse a particular segment --
181 * if the message is correct then it can't go after a required position of a
182 * different name.
183 */
184 public static boolean matchExistsAfterPosition(Position pos, String name, boolean firstDescendentsOnly, boolean upToFirstRequired) throws HL7Exception {
185 boolean matchExists = false;
186
187 //check next rep of self (if any)
188 if (pos.parent.isRepeating(pos.index.name)) {
189 Structure s = pos.parent.get(pos.index.name, pos.index.rep);
190 matchExists = contains(s, name, firstDescendentsOnly, upToFirstRequired);
191 }
192
193 //check later siblings (if any)
194 if (!matchExists) {
195 String[] siblings = pos.parent.getNames();
196 boolean after = false;
197 for (int i = 0; i < siblings.length && !matchExists; i++) {
198 if (after) {
199 matchExists = contains(pos.parent.get(siblings[i]), name, firstDescendentsOnly, upToFirstRequired);
200 if (upToFirstRequired && pos.parent.isRequired(siblings[i])) break;
201 }
202 if (pos.index.name.equals(siblings[i])) after = true;
203 }
204 }
205
206 //recurse to parent (if parent is not message root)
207 if (!matchExists && !Message.class.isAssignableFrom(pos.parent.getClass())) {
208 Group grandparent = pos.parent.getParent();
209 Position parentPos = new Position(grandparent, getIndex(grandparent, pos.parent));
210 matchExists = matchExistsAfterPosition(parentPos, name, firstDescendentsOnly, upToFirstRequired);
211 }
212 log.debug("Match exists after position {} for {}? {}", pos, name, matchExists);
213 return matchExists;
214 }
215
216 /**
217 * Sets the next position to a new segment of the given name, within the
218 * given group.
219 */
220 private void newSegment(Group parent, String name) throws HL7Exception {
221 log.info("MessageIterator creating new segment: {}", name);
222 parent.addNonstandardSegment(name);
223 next = new Position(parent, parent.getNames()[parent.getNames().length-1], 0);
224 }
225
226 /**
227 * Determines whether the given structure matches the given name, or contains
228 * a child that does.
229 * @param s the structure to check
230 * @param name the name to look for
231 * @param firstDescendentsOnly only checks first descendents (i.e. first
232 * child, first child of first child, etc.) In theory the first child
233 * of a group should always be present, and we don't use this method with
234 * subsequent children because finding the next position within a group is
235 * straightforward.
236 * @param upToFirstRequired only checks first descendents and of their siblings
237 * up to the first required one. This may be needed because in practice
238 * some first children of groups are not required.
239 */
240 public static boolean contains(Structure s, String name, boolean firstDescendentsOnly, boolean upToFirstRequired) {
241 boolean contains = false;
242 if (Segment.class.isAssignableFrom(s.getClass())) {
243 if (s.getName().equals(name)) contains = true;
244 } else {
245 Grouphref="../../../../ca/uhn/hl7v2/model/Group.html#Group">Group g = (Group) s;
246 String[] names = g.getNames();
247 for (int i = 0; i < names.length && !contains; i++) {
248 try {
249 contains = contains(g.get(names[i], 0), name, firstDescendentsOnly, upToFirstRequired);
250 if (firstDescendentsOnly) break;
251 if (upToFirstRequired && g.isRequired(names[i])) break;
252 } catch (HL7Exception e) {
253 throw new Error("HL7Exception due to bad index: " + e.getMessage());
254 }
255 }
256 }
257 return contains;
258 }
259
260 /**
261 * Tests whether the name of the given Index matches
262 * the name of the last child of the given group.
263 */
264 public static boolean isLast(Position p) {
265 String[] names = p.parent.getNames();
266 return names[names.length-1].equals(p.index.name);
267 }
268
269 /**
270 * Sets the next location to the next sibling of the given
271 * index.
272 */
273 private void nextSibling(Position pos) {
274 String[] names = pos.parent.getNames();
275 int i = 0;
276 for (; i < names.length && !names[i].equals(pos.index.name); i++) {}
277 String nextName = names[i+1];
278
279 this.next = new Position(pos.parent, nextName, 0);
280 }
281
282 /**
283 * <p>Returns the next node in the message. Sometimes the next node is
284 * ambiguous. For example at the end of a repeating group, the next node
285 * may be the first segment in the next repetition of the group, or the
286 * next sibling, or an undeclared segment locally added to the group's end.
287 * Cases like this are disambiguated using getDirection(), which returns
288 * the name of the structure that we are "iterating towards".
289 * Usually we are "iterating towards" a segment of a certain name because we
290 * have a segment string that we would like to parse into that node.
291 * Here are the rules: </p>
292 * <ol><li>If at a group, next means first child.</li>
293 * <li>If at a non-repeating segment, next means next "position"</li>
294 * <li>If at a repeating segment: if segment name matches
295 * direction then next means next rep, otherwise next means next "position".</li>
296 * <li>If at a segment within a group (not at the end of the group), next "position"
297 * means next sibling</li>
298 * <li>If at the end of a group: If name of group or any of its "first
299 * decendents" matches direction, then next position means next rep of group. Otherwise
300 * if direction matches name of next sibling of the group, or any of its first
301 * descendents, next position means next sibling of the group. Otherwise, next means a
302 * new segment added to the group (with a name that matches "direction"). </li>
303 * <li>"First descendents" means first child, or first child of the first child,
304 * or first child of the first child of the first child, etc. </li> </ol>
305 */
306 public Structure next() {
307 if (!hasNext()) {
308 throw new NoSuchElementException("No more nodes in message");
309 }
310 try {
311 this.currentStructure = next.parent.get(next.index.name, next.index.rep);
312 } catch (HL7Exception e) {
313 throw new NoSuchElementException("HL7Exception: " + e.getMessage());
314 }
315 clearNext();
316 return this.currentStructure;
317 }
318
319 /** Not supported */
320 public void remove() {
321 throw new UnsupportedOperationException("Can't remove a node from a message");
322 }
323
324 public String getDirection() {
325 return this.direction;
326 }
327
328 public void setDirection(String direction) {
329 clearNext();
330 this.direction = direction;
331 }
332
333 private void clearNext() {
334 next = null;
335 }
336
337 /**
338 * Returns the index of the given structure as a child of the
339 * given parent. Returns null if the child isn't found.
340 */
341 public static Index getIndex(Group parent, Structure child) {
342 Index index = null;
343 String[] names = parent.getNames();
344 findChild :
345 for (String name : names) {
346 if (name.startsWith(child.getName())) {
347 try {
348 Structure[] reps = parent.getAll(name);
349 for (int j = 0; j < reps.length; j++) {
350 if (child.equals(reps[j])) {
351 index = new Index(name, j);
352 break findChild;
353 }
354 }
355 } catch (HL7Exception e) {
356 log.error(e.getMessage(), e);
357 throw new Error("Internal HL7Exception finding structure index: " + e.getMessage());
358 }
359 }
360 }
361 return index;
362 }
363
364 /**
365 * An index of a child structure within a group, consisting of the name and rep of
366 * of the child.
367 */
368 public static class Index {
369 public final String name;
370 public final int rep;
371 public Index(String name, int rep) {
372 this.name = name;
373 this.rep = rep;
374 }
375
376 /** @see Object#equals */
377 public boolean equals(Object o) {
378 boolean equals = false;
379 if (o instanceof Index) {
380 Index i = (Index) o;
381 if (i.rep == rep && i.name.equals(name)) equals = true;
382 }
383 return equals;
384 }
385
386 /** @see Object#hashCode */
387 public int hashCode() {
388 return name.hashCode() + 700 * rep;
389 }
390
391 /** @see Object#toString */
392 public String toString() {
393 return this.name + ":" + this.rep;
394 }
395 }
396
397 /**
398 * A structure position within a message.
399 */
400 public static class Position {
401 public final Group parent;
402 public final Index index;
403 public Position(Group parent, String name, int rep) {
404 this.parent = parent;
405 this.index = new Index(name, rep);
406 }
407 public Position(Group parent, Index i) {
408 this.parent = parent;
409 this.index = i;
410 }
411
412 /** @see Object#equals */
413 public boolean equals(Object o) {
414 boolean equals = false;
415 if (o instanceof Position) {
416 Position p = (Position) o;
417 if (p.parent.equals(parent) && p.index.equals(index)) equals = true;
418 }
419 return equals;
420 }
421
422 /** @see Object#hashCode */
423 public int hashCode() {
424 return parent.hashCode() + index.hashCode();
425 }
426
427 public String toString() {
428 return parent.getName() + ":" +
429 index.name +
430 "(" +
431 index.rep +
432 ")";
433 }
434 }
435 }