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    }