Coverage Report - ca.uhn.hl7v2.util.MessageNavigator
 
Classes in this File Line Coverage Branch Coverage Complexity
MessageNavigator
73%
69/94
59%
26/44
3.062
MessageNavigator$GroupContext
100%
4/4
N/A
3.062
 
 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  1772
     public MessageNavigator(Group root) {
 64  1772
         this.root = root;
 65  1772
         reset();
 66  1772
     }
 67  
     
 68  
     public Group getRoot() {
 69  3155
         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  4482
         if (childNumber != -1) {
 80  2348
             Structure s = currentGroup.get(childNames[childNumber], rep);
 81  2348
             if (!(s instanceof Group)) {
 82  0
                 throw new HL7Exception("Can't drill into segment");
 83  
             }
 84  2348
             Group group = (Group) s;
 85  
             
 86  
             //stack the current group and location
 87  2348
             GroupContext gc = new GroupContext(this.currentGroup, this.currentChild);
 88  2348
             this.ancestors.push(gc);
 89  
             
 90  2348
             this.currentGroup = group;
 91  
         }
 92  
         
 93  4482
         this.currentChild = 0;
 94  4482
         this.childNames = this.currentGroup.getNames();
 95  4482
     }
 96  
     
 97  
     /**
 98  
      * Drills down into the group at the CURRENT location.
 99  
      */
 100  
     public void drillDown(int rep) throws HL7Exception {
 101  2134
         drillDown(this.currentChild, rep);
 102  2134
     }
 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  0
         if (!this.ancestors.empty()) {
 112  0
             GroupContext gc = (GroupContext) this.ancestors.pop();
 113  0
             this.currentGroup = gc.group;
 114  0
             this.currentChild = gc.child;
 115  0
             this.childNames = this.currentGroup.getNames();
 116  0
             return true;
 117  
         } else {
 118  0
             if (this.currentChild == -1) {
 119  0
                 return false;
 120  
             } else {
 121  0
                 this.currentChild = -1;
 122  0
                 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  0
         if (this.childNames.length > this.currentChild + 1) {
 132  0
             return true;
 133  
         } else {
 134  0
             return false;
 135  
         }
 136  
     }
 137  
     
 138  
     /**
 139  
      * Moves to the next sibling of the current location.
 140  
      */
 141  
     public void nextChild() throws HL7Exception {
 142  5
         int child = this.currentChild + 1;
 143  5
         toChild(child);
 144  5
     }
 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  4835
         if (child >= 0 && child < this.childNames.length) {
 152  4835
             this.currentChild = child;
 153  4835
             return this.childNames[child];
 154  
         } else {
 155  0
             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  4591
         this.ancestors = new Stack<GroupContext>();
 162  4591
         this.currentGroup = root;
 163  4591
         this.currentChild = -1;
 164  4591
         this.childNames = currentGroup.getNames();
 165  4591
     }
 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  11400
         Structure ret = null;
 173  11400
         if (this.currentChild != -1) {
 174  9266
             String childName = this.childNames[this.currentChild];
 175  9266
             ret = this.currentGroup.get(childName, rep);
 176  9266
         } else { 
 177  2134
             ret = this.currentGroup;
 178  
         }
 179  11400
         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  0
         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  0
         if (this.currentGroup == this.root && this.currentChild == -1) 
 196  0
             throw new HL7Exception("Pointer is at root of navigator: there is no current child");
 197  
         
 198  0
         String childName = this.childNames[this.currentChild];
 199  0
         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  1676
         Structure start = null;
 215  
         
 216  1676
         if (this.currentChild == -1) {
 217  270
             start = this.currentGroup; 
 218  
         } else {
 219  1406
             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  1676
         Iterator<Structure> it = new MessageIterator(start, "doesn't exist", false);
 225  1676
         if (segmentsOnly) {
 226  18
             it = new FilterIterator<Structure>(it, new StructurePredicate(Segment.class));
 227  
         }
 228  
         
 229  1676
         if (it.hasNext()) {
 230  1676
             Structure next = it.next();
 231  1676
             return drillHere(next);
 232  0
         } else if (loop) {
 233  0
             this.reset();
 234  0
             return "";
 235  
         } else {
 236  0
             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  1676
         Structure pathElem = destination;
 247  1676
         Stack<Structure> pathStack = new Stack<Structure>();
 248  1676
         Stack<MessageIterator.Index> indexStack = new Stack<MessageIterator.Index>();
 249  
         do {
 250  4023
             MessageIterator.Index index = MessageIterator.getIndex(pathElem.getParent(), pathElem);
 251  4023
             indexStack.push(index);
 252  4023
             pathElem = pathElem.getParent();
 253  4023
             pathStack.push(pathElem);
 254  4023
         } while (!root.equals(pathElem) && !Message.class.isAssignableFrom(pathElem.getClass()));
 255  
         
 256  1676
         if (!root.equals(pathElem)) {
 257  0
             throw new HL7Exception("The destination provided is not under the root of this navigator");
 258  
         }
 259  
         
 260  1676
         this.reset();
 261  1676
         String retVal = null;
 262  5699
         while (!pathStack.isEmpty()) {
 263  4023
             Group parent = (Group) pathStack.pop();
 264  4023
             MessageIterator.Index index = indexStack.pop();
 265  4023
             int child = search(parent.getNames(), index.name);
 266  4023
             if (!pathStack.isEmpty()) {
 267  2347
                 this.drillDown(child, 0);
 268  
             } else {
 269  1676
                 retVal= this.toChild(child);
 270  
             }
 271  4023
         }
 272  
         
 273  1676
         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  4023
         int found = -1;
 280  12841
         for (int i = 0; i < list.length && found == -1; i++) {
 281  8818
             if (list[i].equals(item)) found = i;
 282  
         }
 283  4023
         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  2348
         public GroupContext(Group g, int c) {
 296  2348
             group = g;
 297  2348
             child = c;
 298  2348
         }
 299  
     }
 300  
     
 301  
 }