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 final 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="../../../../ca/uhn/hl7v2/model/Group.html#Group">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 = 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 return (this.childNames.length > this.currentChild + 1);
132 }
133
134 /**
135 * Moves to the next sibling of the current location.
136 */
137 public void nextChild() throws HL7Exception {
138 toChild(this.currentChild + 1);
139 }
140
141 /**
142 * Moves to the sibling of the current location at the specified index.
143 * @return
144 */
145 public String toChild(int child) throws HL7Exception {
146 if (child >= 0 && child < this.childNames.length) {
147 this.currentChild = child;
148 return this.childNames[child];
149 } else {
150 throw new HL7Exception("Can't advance to child " + child + " -- only " + this.childNames.length + " children");
151 }
152 }
153
154 /** Resets the location to the beginning of the tree (the root) */
155 public void reset() {
156 this.ancestors = new Stack<>();
157 this.currentGroup = root;
158 this.currentChild = -1;
159 this.childNames = currentGroup.getNames();
160 }
161
162 /**
163 * Returns the given rep of the structure at the current location.
164 * If at root, always returns the root (the rep is ignored).
165 */
166 public Structure getCurrentStructure(int rep) throws HL7Exception {
167 if (this.currentChild != -1) {
168 String childName = this.childNames[this.currentChild];
169 return this.currentGroup.get(childName, rep);
170 }
171 return this.currentGroup;
172 }
173
174 /**
175 * Returns the group within which the pointer is currently located.
176 * If at the root, the root is returned.
177 */
178 public Group getCurrentGroup() {
179 return this.currentGroup;
180 }
181
182 /**
183 * Returns the array of structures at the current location.
184 * Throws an exception if pointer is at root.
185 */
186 public Structure[] getCurrentChildReps() throws HL7Exception {
187 if (this.currentGroup == this.root && this.currentChild == -1)
188 throw new HL7Exception("Pointer is at root of navigator: there is no current child");
189
190 String childName = this.childNames[this.currentChild];
191 return this.currentGroup.getAll(childName);
192 }
193
194 /**
195 * Iterates through the message tree to the next segment/group location (regardless
196 * of whether an instance of the segment exists). If the end of the tree is
197 * reached, starts over at the root. Only enters the first repetition of a
198 * repeating group -- explicit navigation (using the drill...() methods) is
199 * necessary to get to subsequent reps.
200 * @param segmentsOnly if true, only stops at segments (not groups)
201 * @param loop if true, loops back to beginning when end of msg reached; if false,
202 * throws HL7Exception if end of msg reached
203 * @return Returns the name of the next item within its parent, or "" for the root (message)
204 */
205 public String iterate(boolean segmentsOnly, boolean loop) throws HL7Exception {
206 Structure start;
207
208 if (this.currentChild == -1) {
209 start = this.currentGroup;
210 } else {
211 start = (this.currentGroup.get(this.childNames[this.currentChild]));
212 }
213
214 //using a non-existent direction and not allowing segment creation means that only
215 //the first rep of anything is traversed.
216 Iterator<Structure> it = new MessageIterator(start, "doesn't exist", false);
217 if (segmentsOnly) {
218 it = new FilterIterator<>(it, new StructurePredicate(Segment.class));
219 }
220
221 if (it.hasNext()) {
222 Structure next = it.next();
223 return drillHere(next);
224 } else if (loop) {
225 this.reset();
226 return "";
227 } else {
228 throw new HL7Exception("End of message reached while iterating without loop");
229 }
230
231 }
232
233 /**
234 * Navigates to a specific location in the message
235 * @return
236 */
237 private String drillHere(Structure destination) throws HL7Exception {
238 Structure pathElem = destination;
239 Stack<Structure> pathStack = new Stack<>();
240 Stack<MessageIterator.Index> indexStack = new Stack<>();
241 do {
242 MessageIterator.Index index = MessageIterator.getIndex(pathElem.getParent(), pathElem);
243 indexStack.push(index);
244 pathElem = pathElem.getParent();
245 pathStack.push(pathElem);
246 } while (!root.equals(pathElem) && !Message.class.isAssignableFrom(pathElem.getClass()));
247
248 if (!root.equals(pathElem)) {
249 throw new HL7Exception("The destination provided is not under the root of this navigator");
250 }
251
252 this.reset();
253 String retVal = null;
254 while (!pathStack.isEmpty()) {
255 Group"../../../../ca/uhn/hl7v2/model/Group.html#Group">Group parent = (Group) pathStack.pop();
256 MessageIterator.Index index = indexStack.pop();
257 int child = search(parent.getNames(), index.name);
258 if (!pathStack.isEmpty()) {
259 this.drillDown(child, 0);
260 } else {
261 retVal= this.toChild(child);
262 }
263 }
264
265 return retVal;
266 }
267
268 /** Like Arrays.binarySearch, only probably slower and doesn't require
269 * a sorted list. Also just returns -1 if item isn't found. */
270 private int search(Object[] list, Object item) {
271 int found = -1;
272 for (int i = 0; i < list.length && found == -1; i++) {
273 if (list[i].equals(item)) found = i;
274 }
275 return found;
276 }
277
278 /**
279 * A structure to hold current location information at
280 * one level of the message tree. A stack of these
281 * identifies the current location completely.
282 */
283 private static class GroupContext {
284 public final Group group;
285 public final int child;
286
287 public GroupContext(Group g, int c) {
288 group = g;
289 child = c;
290 }
291 }
292
293 }