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.trace("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.trace("hasNext() current position: {}", currentPosition);
102
103            IStructureDefinition structureDefinition = currentPosition.getStructureDefinition();
104            
105            if (myMessage.getParser().getParserConfiguration().isNonGreedyMode()) {
106                IStructureDefinition nonGreedyPosition = couldBeNotGreedy();
107                if (nonGreedyPosition != null) {
108                        log.info("Found non greedy parsing choice, moving to {}", nonGreedyPosition.getName());
109                        while (getCurrentPosition().getStructureDefinition() != nonGreedyPosition) {
110                                myCurrentDefinitionPath.remove(myCurrentDefinitionPath.size() - 1);
111                        }
112                }
113            }
114            
115            if (structureDefinition.isSegment() && structureDefinition.getName().startsWith(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) {
116                myNextIsSet = true;
117                currentPosition.incrementRep();
118            } else if (structureDefinition.isSegment() && structureDefinition.getNextLeaf() == null
119                    && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) {
120                if (!myHandleUnexpectedSegments) {
121                    return false;
122                }
123                addNonStandardSegmentAtCurrentPosition();
124            } else if (structureDefinition.hasChildren() && structureDefinition.getAllPossibleFirstChildren().contains(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) {
125                currentPosition.incrementRep();
126                myCurrentDefinitionPath.add(new Position(structureDefinition.getFirstChild(), -1));
127            } else if (!structureDefinition.hasChildren() && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) {
128                if (!myHandleUnexpectedSegments) {
129                    return false;
130                }
131                addNonStandardSegmentAtCurrentPosition();
132                // } else if (structureDefinition.isMessage()) {
133                // if (!handleUnexpectedSegments) {
134                // return false;
135                // }
136                // addNonStandardSegmentAtCurrentPosition();
137            } else if (structureDefinition.isFinalChildOfParent()) {
138                List<Position> newDefinitionPath = popUntilMatchFound(myCurrentDefinitionPath);
139                if (newDefinitionPath != null) {
140                    // found match
141                    myCurrentDefinitionPath = newDefinitionPath;
142                } else {
143                    if (!myHandleUnexpectedSegments) {
144                        return false;
145                    }
146                    addNonStandardSegmentAtCurrentPosition();
147                }
148            } else {
149                currentPosition.setStructureDefinition(structureDefinition.getNextSibling());
150                currentPosition.resetRepNumber();
151            }
152
153        }
154
155        return true;
156    }
157
158    /**
159     * @see ParserConfiguration#setNonGreedyMode(boolean)
160     */
161    private IStructureDefinition couldBeNotGreedy() {
162        for (int i = myCurrentDefinitionPath.size() - 1; i >= 1; i--) {
163                Position position = myCurrentDefinitionPath.get(i);
164                IStructureDefinition curPos = position.getStructureDefinition();
165                if (curPos.getPosition() > 0) {
166                        IStructureDefinition parent = curPos.getParent();
167                                if (parent.isRepeating() && parent.getAllPossibleFirstChildren().contains(myDirection)) {
168                                return parent;
169                        }
170                }
171                
172        }
173        
174                return null;
175        }
176
177        private void addNonStandardSegmentAtCurrentPosition() throws Error {
178        log.debug("Creating non standard segment {} on group: {}", 
179                        myDirection, getCurrentPosition().getStructureDefinition().getParent().getName());
180        
181        List<Position> parentDefinitionPath;
182        Group parentStructure;
183        
184        switch (myMessage.getParser().getParserConfiguration().getUnexpectedSegmentBehaviour()) {
185        case ADD_INLINE:
186        default:
187                parentDefinitionPath = new ArrayList<Position>(myCurrentDefinitionPath.subList(0, myCurrentDefinitionPath.size() - 1));
188                parentStructure = (Group) navigateToStructure(parentDefinitionPath);
189                break;
190        case DROP_TO_ROOT:
191                parentDefinitionPath = new ArrayList<Position>(myCurrentDefinitionPath.subList(0, 1));
192                parentStructure = myMessage;
193                myCurrentDefinitionPath = myCurrentDefinitionPath.subList(0, 2);
194                break;
195        case THROW_HL7_EXCEPTION:
196                throw new Error(new HL7Exception("Found unknown segment: " + myDirection));
197        }
198        
199        
200        // Current position within parent
201        Position currentPosition = getCurrentPosition();
202                String nameAsItAppearsInParent = currentPosition.getStructureDefinition().getNameAsItAppearsInParent();
203
204                int index = Arrays.asList(parentStructure.getNames()).indexOf(nameAsItAppearsInParent) + 1;
205                
206        String newSegmentName;
207                
208                // Check if the structure already has a non-standard segment in the appropriate
209                // position
210                String[] currentNames = parentStructure.getNames();
211                if (index < currentNames.length && currentNames[index].startsWith(myDirection)) {
212                        newSegmentName = currentNames[index];
213                } else { 
214                try {
215                    newSegmentName = parentStructure.addNonstandardSegment(myDirection, index);
216                } catch (HL7Exception e) {
217                    throw new Error("Unable to add nonstandard segment " + myDirection + ": ", e);
218                }
219            }
220                
221        IStructureDefinition previousSibling = getCurrentPosition().getStructureDefinition();
222        IStructureDefinition parentStructureDefinition = parentDefinitionPath.get(parentDefinitionPath.size() - 1).getStructureDefinition();
223        NonStandardStructureDefinition nextDefinition = new NonStandardStructureDefinition(parentStructureDefinition, previousSibling, newSegmentName, index);
224        myCurrentDefinitionPath = parentDefinitionPath;
225        myCurrentDefinitionPath.add(new Position(nextDefinition, 0));
226
227        myNextIsSet = true;
228    }
229
230    /**
231     * <p>
232     * Returns the next node in the message. Sometimes the next node is
233     * ambiguous. For example at the end of a repeating group, the next node may
234     * be the first segment in the next repetition of the group, or the next
235     * sibling, or an undeclared segment locally added to the group's end. Cases
236     * like this are disambiguated using getDirection(), which returns the name
237     * of the structure that we are "iterating towards". Usually we are
238     * "iterating towards" a segment of a certain name because we have a segment
239     * string that we would like to parse into that node. Here are the rules:
240     * </p>
241     * <ol>
242     * <li>If at a group, next means first child.</li>
243     * <li>If at a non-repeating segment, next means next "position"</li>
244     * <li>If at a repeating segment: if segment name matches direction then
245     * next means next rep, otherwise next means next "position".</li>
246     * <li>If at a segment within a group (not at the end of the group), next
247     * "position" means next sibling</li>
248     * <li>If at the end of a group: If name of group or any of its "first
249     * decendents" matches direction, then next position means next rep of
250     * group. Otherwise if direction matches name of next sibling of the group,
251     * or any of its first descendents, next position means next sibling of the
252     * group. Otherwise, next means a new segment added to the group (with a
253     * name that matches "direction").</li>
254     * <li>"First descendents" means first child, or first child of the first
255     * child, or first child of the first child of the first child, etc.</li>
256     * </ol>
257     */
258    public Structure next() {
259        if (!hasNext()) {
260            throw new NoSuchElementException("No more nodes in message");
261        }
262
263        Structure currentStructure = navigateToStructure(myCurrentDefinitionPath);
264
265        clearNext();
266        return currentStructure;
267    }
268
269    private Structure navigateToStructure(List<Position> theDefinitionPath) throws Error {
270        Structure currentStructure = null;
271        for (Position next : theDefinitionPath) {
272            if (currentStructure == null) {
273                currentStructure = myMessage;
274            } else {
275                try {
276                    IStructureDefinition structureDefinition = next.getStructureDefinition();
277                    Group currentStructureGroup = (Group) currentStructure;
278                    String nextStructureName = structureDefinition.getNameAsItAppearsInParent();
279                    currentStructure = currentStructureGroup.get(nextStructureName, next.getRepNumber());
280                } catch (HL7Exception e) {
281                    throw new Error("Failed to retrieve structure: ", e);
282                }
283            }
284        }
285        return currentStructure;
286    }
287
288    /** Not supported */
289    public void remove() {
290        throw new UnsupportedOperationException("Can't remove a node from a message");
291    }
292
293    public String getDirection() {
294        return this.myDirection;
295    }
296
297    public void setDirection(String direction) {
298        clearNext();
299        this.myDirection = direction;
300    }
301
302    private void clearNext() {
303        myNextIsSet = false;
304    }
305
306    /**
307     * A structure position within a message.
308     */
309    public static class Position {
310        private IStructureDefinition myStructureDefinition;
311        private int myRepNumber = -1;
312
313        public IStructureDefinition getStructureDefinition() {
314            return myStructureDefinition;
315        }
316
317        public void resetRepNumber() {
318            myRepNumber = -1;
319        }
320
321        public void setStructureDefinition(IStructureDefinition theStructureDefinition) {
322            myStructureDefinition = theStructureDefinition;
323        }
324
325        public int getRepNumber() {
326            return myRepNumber;
327        }
328
329        public Position(IStructureDefinition theStructureDefinition, int theRepNumber) {
330            myStructureDefinition = theStructureDefinition;
331            myRepNumber = theRepNumber;
332        }
333
334        public void incrementRep() {
335            myRepNumber++;
336        }
337
338        /** @see Object#equals */
339        public boolean equals(Object o) {
340            boolean equals = false;
341            if (o != null && o instanceof Position) {
342                Position p = (Position) o;
343                if (p.myStructureDefinition.equals(myStructureDefinition) && p.myRepNumber == myRepNumber)
344                    equals = true;
345            }
346            return equals;
347        }
348
349        /** @see Object#hashCode */
350        public int hashCode() {
351            return myStructureDefinition.hashCode() + myRepNumber;
352        }
353
354        public String toString() {
355            StringBuilder ret = new StringBuilder();
356
357            if (myStructureDefinition.getParent() != null) {
358                ret.append(myStructureDefinition.getParent().getName());
359            } else {
360                ret.append("Root");
361            }
362
363            ret.append(":");
364            ret.append(myStructureDefinition.getName());
365            ret.append("(");
366            ret.append(myRepNumber);
367            ret.append(")");
368            return ret.toString();
369        }
370    }
371
372    /**
373     * Must be called after {@link #next()}
374     */
375    public int getNextIndexWithinParent() {
376        return getCurrentPosition().getStructureDefinition().getPosition();
377    }
378}