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<GroupContext> 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<GroupContext>();
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<Structure> it = new MessageIterator(start, "doesn't exist", false);
223 if (segmentsOnly) {
224 FilterIterator.Predicate<Structure> predicate = new FilterIterator.Predicate<Structure>() {
225 public boolean evaluate(Structure obj) {
226 if (Segment.class.isAssignableFrom(obj.getClass())) {
227 return true;
228 } else {
229 return false;
230 }
231 }
232 };
233 it = new FilterIterator<Structure>(it, predicate);
234 }
235
236 if (it.hasNext()) {
237 Structure next = 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<Structure> pathStack = new Stack<Structure>();
254 Stack<MessageIterator.Index> indexStack = new Stack<MessageIterator.Index>();
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 = 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 }