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}