001    package ca.uhn.hl7v2.parser;
002    
003    import java.util.ArrayList;
004    import java.util.List;
005    import java.util.NoSuchElementException;
006    
007    import ca.uhn.hl7v2.HL7Exception;
008    import ca.uhn.hl7v2.model.Group;
009    import ca.uhn.hl7v2.model.Message;
010    import ca.uhn.hl7v2.model.Structure;
011    import ca.uhn.log.HapiLog;
012    import ca.uhn.log.HapiLogFactory;
013    
014    /**
015     * Iterates over all defined nodes (ie segments, groups) in a message,
016     * regardless of whether they have been instantiated previously. This is a
017     * tricky process, because the number of nodes is infinite, due to infinitely
018     * repeating segments and groups. See <code>next()</code> for details on how
019     * this is handled.
020     * 
021     * This implementation assumes that the first segment in each group is present
022     * (as per HL7 rules). Specifically, when looking for a segment location, an
023     * empty group that has a spot for the segment will be overlooked if there is
024     * anything else before that spot. This may result in surprising (but sensible)
025     * behaviour if a message is missing the first segment in a group.
026     * 
027     * @author Bryan Tripp
028     */
029    public class MessageIterator implements java.util.Iterator<Structure> {
030    
031        private Message myMessage;
032        private String myDirection;
033        private boolean myNextIsSet;
034        private boolean myHandleUnexpectedSegments;
035        private List<Position> myCurrentDefinitionPath = new ArrayList<Position>();
036    
037        private static final HapiLog log = HapiLogFactory.getHapiLog(MessageIterator.class);
038    
039        /*
040         * may add configurability later ... private boolean findUpToFirstRequired;
041         * private boolean findFirstDescendentsOnly;
042         * 
043         * public static final String WHOLE_GROUP; public static final String
044         * FIRST_DESCENDENTS_ONLY; public static final String UP_TO_FIRST_REQUIRED;
045         */
046    
047        /** Creates a new instance of MessageIterator */
048        public MessageIterator(Message start, IStructureDefinition startDefinition, String direction, boolean handleUnexpectedSegments) {
049            this.myMessage = start;
050            this.myDirection = direction;
051            this.myHandleUnexpectedSegments = handleUnexpectedSegments;
052            this.myCurrentDefinitionPath.add(new Position(startDefinition, 0));
053        }
054    
055        private Position getCurrentPosition() {
056            return getTail(myCurrentDefinitionPath);
057        }
058    
059        private Position getTail(List<Position> theDefinitionPath) {
060            return theDefinitionPath.get(theDefinitionPath.size() - 1);
061        }
062    
063        private List<Position> popUntilMatchFound(List<Position> theDefinitionPath) {
064            theDefinitionPath = new ArrayList<Position>(theDefinitionPath.subList(0, theDefinitionPath.size() - 1));
065    
066            Position newCurrentPosition = getTail(theDefinitionPath);
067            IStructureDefinition newCurrentStructureDefinition = newCurrentPosition.getStructureDefinition();
068    
069            if (newCurrentStructureDefinition.getAllPossibleFirstChildren().contains(myDirection)) {
070                return theDefinitionPath;
071            }
072    
073            if (newCurrentStructureDefinition.isFinalChildOfParent()) {
074                if (theDefinitionPath.size() > 1) {
075                    return popUntilMatchFound(theDefinitionPath); // recurse
076                } else {
077                    if (log.isDebugEnabled()) {
078                        log.debug("Popped to root of message and did not find a match for " + myDirection);
079                    }
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()");
093            if (myDirection == null) {
094                throw new IllegalStateException("Direction not set");
095            }
096    
097            while (!myNextIsSet) {
098    
099                Position currentPosition = getCurrentPosition();
100    
101                if (log.isDebugEnabled()) {
102                    log.debug("hasNext() current position: " + currentPosition);
103                }
104    
105                IStructureDefinition structureDefinition = currentPosition.getStructureDefinition();
106                if (structureDefinition.getName().equals(myDirection) && (structureDefinition.isRepeating() || currentPosition.getRepNumber() == -1)) {
107                    myNextIsSet = true;
108                    currentPosition.incrementRep();
109                } else if (structureDefinition.isSegment() && structureDefinition.getNextLeaf() == null
110                        && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) {
111                    if (!myHandleUnexpectedSegments) {
112                        return false;
113                    }
114                    addNonStandardSegmentAtCurrentPosition();
115                } else if (structureDefinition.hasChildren() && structureDefinition.getAllPossibleFirstChildren().contains(myDirection)) {
116                    currentPosition.incrementRep();
117                    myCurrentDefinitionPath.add(new Position(structureDefinition.getFirstChild(), -1));
118                } else if (!structureDefinition.hasChildren() && !structureDefinition.getNamesOfAllPossibleFollowingLeaves().contains(myDirection)) {
119                    if (!myHandleUnexpectedSegments) {
120                        return false;
121                    }
122                    addNonStandardSegmentAtCurrentPosition();
123                    // } else if (structureDefinition.isMessage()) {
124                    // if (!handleUnexpectedSegments) {
125                    // return false;
126                    // }
127                    // addNonStandardSegmentAtCurrentPosition();
128                } else if (structureDefinition.isFinalChildOfParent()) {
129                    List<Position> newDefinitionPath = popUntilMatchFound(myCurrentDefinitionPath);
130                    if (newDefinitionPath != null) {
131                        // found match
132                        myCurrentDefinitionPath = newDefinitionPath;
133                    } else {
134                        if (!myHandleUnexpectedSegments) {
135                            return false;
136                        }
137                        addNonStandardSegmentAtCurrentPosition();
138                    }
139                } else {
140                    currentPosition.setStructureDefinition(structureDefinition.getNextSibling());
141                    currentPosition.resetRepNumber();
142                }
143    
144            }
145    
146            return true;
147        }
148    
149        private void addNonStandardSegmentAtCurrentPosition() throws Error {
150            if (log.isDebugEnabled()) {
151                log.debug("Creating non standard segment on group: " + getCurrentPosition().getStructureDefinition().getParent().getName());
152            }
153            List<Position> parentDefinitionPath = new ArrayList<Position>(myCurrentDefinitionPath.subList(0, myCurrentDefinitionPath.size() - 1));
154            Group parentStructure = (Group) navigateToStructure(parentDefinitionPath);
155    
156            int index = getCurrentPosition().getStructureDefinition().getPosition() + 1;
157            String newSegmentName;
158            try {
159                newSegmentName = parentStructure.addNonstandardSegment(myDirection, index);
160            } catch (HL7Exception e) {
161                throw new Error("Unable to add nonstandard segment " + myDirection + ": ", e);
162            }
163            IStructureDefinition previousSibling = getCurrentPosition().getStructureDefinition();
164            IStructureDefinition parentStructureDefinition = parentDefinitionPath.get(parentDefinitionPath.size() - 1).getStructureDefinition();
165            NonStandardStructureDefinition nextDefinition = new NonStandardStructureDefinition(parentStructureDefinition, previousSibling, newSegmentName, index);
166            myCurrentDefinitionPath = parentDefinitionPath;
167            myCurrentDefinitionPath.add(new Position(nextDefinition, 0));
168    
169            myNextIsSet = true;
170        }
171    
172        /**
173         * <p>
174         * Returns the next node in the message. Sometimes the next node is
175         * ambiguous. For example at the end of a repeating group, the next node may
176         * be the first segment in the next repetition of the group, or the next
177         * sibling, or an undeclared segment locally added to the group's end. Cases
178         * like this are disambiguated using getDirection(), which returns the name
179         * of the structure that we are "iterating towards". Usually we are
180         * "iterating towards" a segment of a certain name because we have a segment
181         * string that we would like to parse into that node. Here are the rules:
182         * </p>
183         * <ol>
184         * <li>If at a group, next means first child.</li>
185         * <li>If at a non-repeating segment, next means next "position"</li>
186         * <li>If at a repeating segment: if segment name matches direction then
187         * next means next rep, otherwise next means next "position".</li>
188         * <li>If at a segment within a group (not at the end of the group), next
189         * "position" means next sibling</li>
190         * <li>If at the end of a group: If name of group or any of its "first
191         * decendents" matches direction, then next position means next rep of
192         * group. Otherwise if direction matches name of next sibling of the group,
193         * or any of its first descendents, next position means next sibling of the
194         * group. Otherwise, next means a new segment added to the group (with a
195         * name that matches "direction").</li>
196         * <li>"First descendents" means first child, or first child of the first
197         * child, or first child of the first child of the first child, etc.</li>
198         * </ol>
199         */
200        public Structure next() {
201            if (!hasNext()) {
202                throw new NoSuchElementException("No more nodes in message");
203            }
204    
205            Structure currentStructure = navigateToStructure(myCurrentDefinitionPath);
206    
207            clearNext();
208            return currentStructure;
209        }
210    
211        private Structure navigateToStructure(List<Position> theDefinitionPath) throws Error {
212            Structure currentStructure = null;
213            for (Position next : theDefinitionPath) {
214                if (currentStructure == null) {
215                    currentStructure = myMessage;
216                } else {
217                    try {
218                        IStructureDefinition structureDefinition = next.getStructureDefinition();
219                        Group currentStructureGroup = (Group) currentStructure;
220                        String nextStructureName = structureDefinition.getNameAsItAppearsInParent();
221                        currentStructure = currentStructureGroup.get(nextStructureName, next.getRepNumber());
222                    } catch (HL7Exception e) {
223                        throw new Error("Failed to retrieve structure: ", e);
224                    }
225                }
226            }
227            return currentStructure;
228        }
229    
230        /** Not supported */
231        public void remove() {
232            throw new UnsupportedOperationException("Can't remove a node from a message");
233        }
234    
235        public String getDirection() {
236            return this.myDirection;
237        }
238    
239        public void setDirection(String direction) {
240            clearNext();
241            this.myDirection = direction;
242        }
243    
244        private void clearNext() {
245            myNextIsSet = false;
246        }
247    
248        /**
249         * A structure position within a message.
250         */
251        public static class Position {
252            private IStructureDefinition myStructureDefinition;
253            private int myRepNumber = -1;
254    
255            public IStructureDefinition getStructureDefinition() {
256                return myStructureDefinition;
257            }
258    
259            public void resetRepNumber() {
260                myRepNumber = -1;
261            }
262    
263            public void setStructureDefinition(IStructureDefinition theStructureDefinition) {
264                myStructureDefinition = theStructureDefinition;
265            }
266    
267            public int getRepNumber() {
268                return myRepNumber;
269            }
270    
271            public Position(IStructureDefinition theStructureDefinition, int theRepNumber) {
272                myStructureDefinition = theStructureDefinition;
273                myRepNumber = theRepNumber;
274            }
275    
276            public void incrementRep() {
277                myRepNumber++;
278            }
279    
280            /** @see Object#equals */
281            public boolean equals(Object o) {
282                boolean equals = false;
283                if (o != null && o instanceof Position) {
284                    Position p = (Position) o;
285                    if (p.myStructureDefinition.equals(myStructureDefinition) && p.myRepNumber == myRepNumber)
286                        equals = true;
287                }
288                return equals;
289            }
290    
291            /** @see Object#hashCode */
292            public int hashCode() {
293                return myStructureDefinition.hashCode() + myRepNumber;
294            }
295    
296            public String toString() {
297                StringBuffer ret = new StringBuffer();
298    
299                if (myStructureDefinition.getParent() != null) {
300                    ret.append(myStructureDefinition.getParent().getName());
301                } else {
302                    ret.append("Root");
303                }
304    
305                ret.append(":");
306                ret.append(myStructureDefinition.getName());
307                ret.append("(");
308                ret.append(myRepNumber);
309                ret.append(")");
310                return ret.toString();
311            }
312        }
313    
314        /**
315         * Must be called after {@link #next()}
316         * 
317         * @return
318         */
319        public int getNextIndexWithinParent() {
320            return getCurrentPosition().getStructureDefinition().getPosition();
321        }
322    }