001 package ca.uhn.hl7v2.parser;
002
003 import java.util.ArrayList;
004 import java.util.Arrays;
005 import java.util.List;
006 import java.util.NoSuchElementException;
007
008 import org.slf4j.Logger;
009 import org.slf4j.LoggerFactory;
010
011 import ca.uhn.hl7v2.HL7Exception;
012 import ca.uhn.hl7v2.model.Group;
013 import ca.uhn.hl7v2.model.Message;
014 import 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 */
031 public 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 }