View Javadoc

1   /**
2    * The contents of this file are subject to the Mozilla Public License Version 1.1
3    * (the "License"); you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at http://www.mozilla.org/MPL/
5    * Software distributed under the License is distributed on an "AS IS" basis,
6    * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
7    * specific language governing rights and limitations under the License.
8    *
9    * The Original Code is "MessageNaviagtor.java".  Description:
10   * "Used to navigate the nested group structure of a message."
11   *
12   * The Initial Developer of the Original Code is University Health Network. Copyright (C)
13   * 2002.  All Rights Reserved.
14   *
15   * Contributor(s): ______________________________________.
16   *
17   * Alternatively, the contents of this file may be used under the terms of the
18   * GNU General Public License (the "GPL"), in which case the provisions of the GPL are
19   * applicable instead of those above.  If you wish to allow use of your version of this
20   * file only under the terms of the GPL and not to allow others to use your version
21   * of this file under the MPL, indicate your decision by deleting  the provisions above
22   * and replace  them with the notice and other provisions required by the GPL License.
23   * If you do not delete the provisions above, a recipient may use your version of
24   * this file under either the MPL or the GPL.
25   *
26   */
27  
28  package ca.uhn.hl7v2.util;
29  
30  import java.util.*;
31  import ca.uhn.hl7v2.model.*;
32  import ca.uhn.hl7v2.HL7Exception;
33  
34  /**
35   * <p>Used to navigate the nested group structure of a message.  This is an alternate
36   * way of accessing parts of a message, ie rather than getting a segment through
37   * a chain of getXXX() calls on the message, you can create a MessageNavigator
38   * for the message, "navigate" to the desired segment, and then call
39   * getCurrentStructure() to get the segment you have navigated to.  A message
40   * navigator always has a "current location" pointing to some structure location (segment
41   * or group location) within the message.  Note that a location exists whether or
42   * not there are any instances of the structure at that location. </p>
43   * <p>This class is used by Terser, which presents an even more convenient way
44   * of navigating a message.  </p>
45   * <p>This class also has an iterate() method, which iterates over
46   * segments (and optionally groups).  </p>
47   * @author Bryan Tripp
48   */
49  public class MessageNavigator {
50      
51      private Group root;
52      private Stack<GroupContext> ancestors;
53      private int currentChild; // -1 means current structure is current group (special case used for root)
54      private Group currentGroup;
55      private String[] childNames;
56      
57      /**
58       * Creates a new instance of MessageNavigator
59       * @param root the root of navigation -- may be a message or a group
60       *      within a message.  Navigation will only occur within the subtree
61       *      of which the given group is the root.
62       */
63      public MessageNavigator(Group root) {
64          this.root = root;
65          reset();
66      }
67      
68      public Group getRoot() {
69          return this.root;
70      }
71         
72      /**
73       * Drills down into the group at the given index within the current
74       * group -- ie sets the location pointer to the first structure within the child
75       * @param childNumber the index of the group child into which to drill
76       * @param rep the group repetition into which to drill
77       */
78      public void drillDown(int childNumber, int rep) throws HL7Exception {
79          if (childNumber != -1) {
80              Structure s = currentGroup.get(childNames[childNumber], rep);
81              if (!(s instanceof Group)) {
82                  throw new HL7Exception("Can't drill into segment");
83              }
84              Group group = (Group) s;
85              
86              //stack the current group and location
87              GroupContext gc = new GroupContext(this.currentGroup, this.currentChild);
88              this.ancestors.push(gc);
89              
90              this.currentGroup = group;
91          }
92          
93          this.currentChild = 0;
94          this.childNames = this.currentGroup.getNames();
95      }
96      
97      /**
98       * Drills down into the group at the CURRENT location.
99       */
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      * @return 
149      */
150     public String toChild(int child) throws HL7Exception {
151         if (child >= 0 && child < this.childNames.length) {
152             this.currentChild = child;
153             return this.childNames[child];
154         } else {
155             throw new HL7Exception("Can't advance to child " + child + " -- only " + this.childNames.length + " children");
156         }
157     }
158     
159     /** Resets the location to the beginning of the tree (the root) */
160     public void reset() {
161         this.ancestors = new Stack<GroupContext>();
162         this.currentGroup = root;
163         this.currentChild = -1;
164         this.childNames = currentGroup.getNames();
165     }
166     
167     /**
168      * Returns the given rep of the structure at the current location.  
169      * If at root, always returns the root (the rep is ignored).  
170      */
171     public Structure getCurrentStructure(int rep) throws HL7Exception {
172         Structure ret = null;
173         if (this.currentChild != -1) {
174             String childName = this.childNames[this.currentChild];
175             ret = this.currentGroup.get(childName, rep);
176         } else { 
177             ret = this.currentGroup;
178         }
179         return ret;
180     }
181     
182     /** 
183      * Returns the group within which the pointer is currently located. 
184      * If at the root, the root is returned.  
185      */
186     public Group getCurrentGroup() {
187         return this.currentGroup;
188     }
189     
190     /**
191      * Returns the array of structures at the current location.  
192      * Throws an exception if pointer is at root.  
193      */
194     public Structure[] getCurrentChildReps() throws HL7Exception {
195         if (this.currentGroup == this.root && this.currentChild == -1) 
196             throw new HL7Exception("Pointer is at root of navigator: there is no current child");
197         
198         String childName = this.childNames[this.currentChild];
199         return this.currentGroup.getAll(childName);
200     }
201     
202     /**
203      * Iterates through the message tree to the next segment/group location (regardless
204      * of whether an instance of the segment exists).  If the end of the tree is
205      * reached, starts over at the root.  Only enters the first repetition of a
206      * repeating group -- explicit navigation (using the drill...() methods) is
207      * necessary to get to subsequent reps.
208      * @param segmentsOnly if true, only stops at segments (not groups)
209      * @param loop if true, loops back to beginning when end of msg reached; if false,
210      *      throws HL7Exception if end of msg reached
211      * @return Returns the name of the next item within its parent, or "" for the root (message)
212      */
213     public String iterate(boolean segmentsOnly, boolean loop) throws HL7Exception { 
214         Structure start = null;
215         
216         if (this.currentChild == -1) {
217             start = this.currentGroup; 
218         } else {
219             start = (this.currentGroup.get(this.childNames[this.currentChild]));
220         }
221         
222         //using a non-existent direction and not allowing segment creation means that only
223         //the first rep of anything is traversed.
224         Iterator<Structure> it = new MessageIterator(start, "doesn't exist", false);
225         if (segmentsOnly) {
226             it = new FilterIterator<Structure>(it, new StructurePredicate(Segment.class));
227         }
228         
229         if (it.hasNext()) {
230             Structure next = it.next();
231             return drillHere(next);
232         } else if (loop) {
233             this.reset();
234             return "";
235         } else {
236             throw new HL7Exception("End of message reached while iterating without loop");
237         }
238             
239     }
240     
241     /**
242      * Navigates to a specific location in the message
243      * @return 
244      */
245     private String drillHere(Structure destination) throws HL7Exception {
246         Structure pathElem = destination;
247         Stack<Structure> pathStack = new Stack<Structure>();
248         Stack<MessageIterator.Index> indexStack = new Stack<MessageIterator.Index>();
249         do {
250             MessageIterator.Index index = MessageIterator.getIndex(pathElem.getParent(), pathElem);
251             indexStack.push(index);
252             pathElem = pathElem.getParent();
253             pathStack.push(pathElem);
254         } while (!root.equals(pathElem) && !Message.class.isAssignableFrom(pathElem.getClass()));
255         
256         if (!root.equals(pathElem)) {
257             throw new HL7Exception("The destination provided is not under the root of this navigator");
258         }
259         
260         this.reset();
261         String retVal = null;
262         while (!pathStack.isEmpty()) {
263             Group parent = (Group) pathStack.pop();
264             MessageIterator.Index index = indexStack.pop();
265             int child = search(parent.getNames(), index.name);
266             if (!pathStack.isEmpty()) {
267                 this.drillDown(child, 0);
268             } else {
269                 retVal= this.toChild(child);
270             }
271         }
272         
273         return retVal;
274     }
275     
276     /** Like Arrays.binarySearch, only probably slower and doesn't require
277      * a sorted list.  Also just returns -1 if item isn't found. */
278     private int search(Object[] list, Object item) {
279         int found = -1;
280         for (int i = 0; i < list.length && found == -1; i++) {
281             if (list[i].equals(item)) found = i;
282         }
283         return found;
284     }
285     
286     /**
287      * A structure to hold current location information at
288      * one level of the message tree.  A stack of these
289      * identifies the current location completely.
290      */
291     private class GroupContext {
292         public Group group;
293         public int child;
294         
295         public GroupContext(Group g, int c) {
296             group = g;
297             child = c;
298         }
299     }
300     
301 }