001    /**
002     * The contents of this file are subject to the Mozilla Public License Version 1.1
003     * (the "License"); you may not use this file except in compliance with the License.
004     * You may obtain a copy of the License at http://www.mozilla.org/MPL/
005     * Software distributed under the License is distributed on an "AS IS" basis,
006     * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
007     * specific language governing rights and limitations under the License.
008     *
009     * The Original Code is "MessageNaviagtor.java".  Description:
010     * "Used to navigate the nested group structure of a message."
011     *
012     * The Initial Developer of the Original Code is University Health Network. Copyright (C)
013     * 2002.  All Rights Reserved.
014     *
015     * Contributor(s): ______________________________________.
016     *
017     * Alternatively, the contents of this file may be used under the terms of the
018     * GNU General Public License (the  ?GPL?), in which case the provisions of the GPL are
019     * applicable instead of those above.  If you wish to allow use of your version of this
020     * file only under the terms of the GPL and not to allow others to use your version
021     * of this file under the MPL, indicate your decision by deleting  the provisions above
022     * and replace  them with the notice and other provisions required by the GPL License.
023     * If you do not delete the provisions above, a recipient may use your version of
024     * this file under either the MPL or the GPL.
025     *
026     */
027    
028    package ca.uhn.hl7v2.util;
029    
030    import java.util.*;
031    import ca.uhn.hl7v2.model.*;
032    import ca.uhn.hl7v2.HL7Exception;
033    
034    /**
035     * <p>Used to navigate the nested group structure of a message.  This is an alternate
036     * way of accessing parts of a message, ie rather than getting a segment through
037     * a chain of getXXX() calls on the message, you can create a MessageNavigator
038     * for the message, "navigate" to the desired segment, and then call
039     * getCurrentStructure() to get the segment you have navigated to.  A message
040     * navigator always has a "current location" pointing to some structure location (segment
041     * or group location) within the message.  Note that a location exists whether or
042     * not there are any instances of the structure at that location. </p>
043     * <p>This class is used by Terser, which presents an even more convenient way
044     * of navigating a message.  </p>
045     * <p>This class also has an iterate() method, which iterates over
046     * segments (and optionally groups).  </p>
047     * @author Bryan Tripp
048     */
049    public class MessageNavigator {
050        
051        private Group root;
052        private Stack ancestors;
053        private int currentChild; // -1 means current structure is current group (special case used for root)
054        private Group currentGroup;
055        private String[] childNames;
056        
057        /**
058         * Creates a new instance of MessageNavigator
059         * @param root the root of navigation -- may be a message or a group
060         *      within a message.  Navigation will only occur within the subtree
061         *      of which the given group is the root.
062         */
063        public MessageNavigator(Group root) {
064            this.root = root;
065            reset();
066        }
067        
068        public Group getRoot() {
069            return this.root;
070        }
071           
072        /**
073         * Drills down into the group at the given index within the current
074         * group -- ie sets the location pointer to the first structure within the child
075         * @param childNumber the index of the group child into which to drill
076         * @param rep the group repetition into which to drill
077         */
078        public void drillDown(int childNumber, int rep) throws HL7Exception {
079            if (childNumber != -1) {
080                Structure s = currentGroup.get(childNames[childNumber], rep);
081                if (!(s instanceof Group)) {
082                    throw new HL7Exception("Can't drill into segment", HL7Exception.APPLICATION_INTERNAL_ERROR);
083                }
084                Group group = (Group) s;
085                
086                //stack the current group and location
087                GroupContext gc = new GroupContext(this.currentGroup, this.currentChild);
088                this.ancestors.push(gc);
089                
090                this.currentGroup = group;
091            }
092            
093            this.currentChild = 0;
094            this.childNames = this.currentGroup.getNames();
095        }
096        
097        /**
098         * Drills down into the group at the CURRENT location.
099         */
100        public void drillDown(int rep) throws HL7Exception {
101            drillDown(this.currentChild, rep);
102        }
103        
104        /**
105         * Switches the group context to the parent of the current group,
106         * and sets the child pointer to the next sibling.
107         * @return false if already at root
108         */
109        public boolean drillUp() {
110            //pop the top group and resume search there
111            if (!this.ancestors.empty()) {
112                GroupContext gc = (GroupContext) this.ancestors.pop();
113                this.currentGroup = gc.group;
114                this.currentChild = gc.child;
115                this.childNames = this.currentGroup.getNames();
116                return true;
117            } else {
118                if (this.currentChild == -1) {
119                    return false;
120                } else {
121                    this.currentChild = -1;
122                    return true;
123                }
124            }
125        }
126        
127        /**
128         * Returns true if there is a sibling following the current location.
129         */
130        public boolean hasNextChild() {
131            if (this.childNames.length > this.currentChild + 1) {
132                return true;
133            } else {
134                return false;
135            }
136        }
137        
138        /**
139         * Moves to the next sibling of the current location.
140         */
141        public void nextChild() throws HL7Exception {
142            int child = this.currentChild + 1;
143            toChild(child);
144        }
145        
146        /**
147         * Moves to the sibling of the current location at the specified index.
148         */
149        public void toChild(int child) throws HL7Exception {
150            if (child >= 0 && child < this.childNames.length) {
151                this.currentChild = child;
152            } else {
153                throw new HL7Exception("Can't advance to child " + child + " -- only " + this.childNames.length + " children",
154                HL7Exception.APPLICATION_INTERNAL_ERROR);
155            }
156        }
157        
158        /** Resets the location to the beginning of the tree (the root) */
159        public void reset() {
160            this.ancestors = new Stack();
161            this.currentGroup = root;
162            this.currentChild = -1;
163            this.childNames = currentGroup.getNames();
164        }
165        
166        /**
167         * Returns the given rep of the structure at the current location.  
168         * If at root, always returns the root (the rep is ignored).  
169         */
170        public Structure getCurrentStructure(int rep) throws HL7Exception {
171            Structure ret = null;
172            if (this.currentChild != -1) {
173                String childName = this.childNames[this.currentChild];
174                ret = this.currentGroup.get(childName, rep);
175            } else { 
176                ret = this.currentGroup;
177            }
178            return ret;
179        }
180        
181        /** 
182         * Returns the group within which the pointer is currently located. 
183         * If at the root, the root is returned.  
184         */
185        public Group getCurrentGroup() {
186            return this.currentGroup;
187        }
188        
189        /**
190         * Returns the array of structures at the current location.  
191         * Throws an exception if pointer is at root.  
192         */
193        public Structure[] getCurrentChildReps() throws HL7Exception {
194            if (this.currentGroup == this.root && this.currentChild == -1) 
195                throw new HL7Exception("Pointer is at root of navigator: there is no current child");
196            
197            String childName = this.childNames[this.currentChild];
198            return this.currentGroup.getAll(childName);
199        }
200        
201        /**
202         * Iterates through the message tree to the next segment/group location (regardless
203         * of whether an instance of the segment exists).  If the end of the tree is
204         * reached, starts over at the root.  Only enters the first repetition of a
205         * repeating group -- explicit navigation (using the drill...() methods) is
206         * necessary to get to subsequent reps.
207         * @param segmentsOnly if true, only stops at segments (not groups)
208         * @param loop if true, loops back to beginning when end of msg reached; if false,
209         *      throws HL7Exception if end of msg reached
210         */
211        public void iterate(boolean segmentsOnly, boolean loop) throws HL7Exception { 
212            Structure start = null;
213            
214            if (this.currentChild == -1) {
215                start = this.currentGroup; 
216            } else {
217                start = (this.currentGroup.get(this.childNames[this.currentChild]));
218            }
219            
220            //using a non-existent direction and not allowing segment creation means that only
221            //the first rep of anything is traversed.
222            Iterator it = new MessageIterator(start, "doesn't exist", false);
223            if (segmentsOnly) {
224                FilterIterator.Predicate predicate = new FilterIterator.Predicate() {
225                    public boolean evaluate(Object obj) {
226                        if (Segment.class.isAssignableFrom(obj.getClass())) {
227                            return true;
228                        } else {
229                            return false;
230                        }
231                    }
232                };
233                it = new FilterIterator(it, predicate);
234            }
235            
236            if (it.hasNext()) {
237                Structure next = (Structure) it.next();
238                drillHere(next);
239            } else if (loop) {
240                this.reset();
241            } else {
242                throw new HL7Exception("End of message reached while iterating without loop", 
243                    HL7Exception.APPLICATION_INTERNAL_ERROR);
244            }
245                
246        }
247        
248        /**
249         * Navigates to a specific location in the message
250         */
251        private void drillHere(Structure destination) throws HL7Exception {
252            Structure pathElem = destination;
253            Stack pathStack = new Stack();
254            Stack indexStack = new Stack();
255            do {
256                MessageIterator.Index index = MessageIterator.getIndex(pathElem.getParent(), pathElem);
257                indexStack.push(index);
258                pathElem = pathElem.getParent();
259                pathStack.push(pathElem);
260            } while (!root.equals(pathElem) && !Message.class.isAssignableFrom(pathElem.getClass()));
261            
262            if (!root.equals(pathElem)) {
263                throw new HL7Exception("The destination provided is not under the root of this navigator");
264            }
265            
266            this.reset();
267            while (!pathStack.isEmpty()) {
268                Group parent = (Group) pathStack.pop();
269                MessageIterator.Index index = (MessageIterator.Index) indexStack.pop();
270                int child = search(parent.getNames(), index.name);
271                if (!pathStack.isEmpty()) {
272                    this.drillDown(child, 0);
273                } else {
274                    this.toChild(child);
275                }
276            }
277        }
278        
279        /** Like Arrays.binarySearch, only probably slower and doesn't require
280         * a sorted list.  Also just returns -1 if item isn't found. */
281        private int search(Object[] list, Object item) {
282            int found = -1;
283            for (int i = 0; i < list.length && found == -1; i++) {
284                if (list[i].equals(item)) found = i;
285            }
286            return found;
287        }
288        
289        /**
290         * Drills down recursively until a segment is reached.
291         */
292        private void findLeaf() throws HL7Exception {
293            if (this.currentChild == -1)
294                this.currentChild = 0;
295            
296            Class c = this.currentGroup.getClass(this.childNames[this.currentChild]);
297            if (Group.class.isAssignableFrom(c)) {
298                drillDown(this.currentChild, 0);
299                findLeaf();
300            }
301        }
302        
303        /**
304         * A structure to hold current location information at
305         * one level of the message tree.  A stack of these
306         * identifies the current location completely.
307         */
308        private class GroupContext {
309            public Group group;
310            public int child;
311            
312            public GroupContext(Group g, int c) {
313                group = g;
314                child = c;
315            }
316        }
317        
318    }