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 }