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