ca.uhn.hl7v2.util.MessageNavigator Maven / Gradle / Ivy
/**
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.mozilla.org/MPL/
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
* specific language governing rights and limitations under the License.
*
* The Original Code is "MessageNaviagtor.java". Description:
* "Used to navigate the nested group structure of a message."
*
* The Initial Developer of the Original Code is University Health Network. Copyright (C)
* 2002. All Rights Reserved.
*
* Contributor(s): ______________________________________.
*
* Alternatively, the contents of this file may be used under the terms of the
* GNU General Public License (the ?GPL?), in which case the provisions of the GPL are
* applicable instead of those above. If you wish to allow use of your version of this
* file only under the terms of the GPL and not to allow others to use your version
* of this file under the MPL, indicate your decision by deleting the provisions above
* and replace them with the notice and other provisions required by the GPL License.
* If you do not delete the provisions above, a recipient may use your version of
* this file under either the MPL or the GPL.
*
*/
package ca.uhn.hl7v2.util;
import java.util.*;
import ca.uhn.hl7v2.model.*;
import ca.uhn.hl7v2.HL7Exception;
/**
* Used to navigate the nested group structure of a message. This is an alternate
* way of accessing parts of a message, ie rather than getting a segment through
* a chain of getXXX() calls on the message, you can create a MessageNavigator
* for the message, "navigate" to the desired segment, and then call
* getCurrentStructure() to get the segment you have navigated to. A message
* navigator always has a "current location" pointing to some structure location (segment
* or group location) within the message. Note that a location exists whether or
* not there are any instances of the structure at that location.
* This class is used by Terser, which presents an even more convenient way
* of navigating a message.
* This class also has an iterate() method, which iterates over
* segments (and optionally groups).
* @author Bryan Tripp
*/
public class MessageNavigator {
private Group root;
private Stack ancestors;
private int currentChild; // -1 means current structure is current group (special case used for root)
private Group currentGroup;
private String[] childNames;
/**
* Creates a new instance of MessageNavigator
* @param root the root of navigation -- may be a message or a group
* within a message. Navigation will only occur within the subtree
* of which the given group is the root.
*/
public MessageNavigator(Group root) {
this.root = root;
reset();
}
public Group getRoot() {
return this.root;
}
/**
* Drills down into the group at the given index within the current
* group -- ie sets the location pointer to the first structure within the child
* @param childNumber the index of the group child into which to drill
* @param rep the group repetition into which to drill
*/
public void drillDown(int childNumber, int rep) throws HL7Exception {
if (childNumber != -1) {
Structure s = currentGroup.get(childNames[childNumber], rep);
if (!(s instanceof Group)) {
throw new HL7Exception("Can't drill into segment", HL7Exception.APPLICATION_INTERNAL_ERROR);
}
Group group = (Group) s;
//stack the current group and location
GroupContext gc = new GroupContext(this.currentGroup, this.currentChild);
this.ancestors.push(gc);
this.currentGroup = group;
}
this.currentChild = 0;
this.childNames = this.currentGroup.getNames();
}
/**
* Drills down into the group at the CURRENT location.
*/
public void drillDown(int rep) throws HL7Exception {
drillDown(this.currentChild, rep);
}
/**
* Switches the group context to the parent of the current group,
* and sets the child pointer to the next sibling.
* @return false if already at root
*/
public boolean drillUp() {
//pop the top group and resume search there
if (!this.ancestors.empty()) {
GroupContext gc = (GroupContext) this.ancestors.pop();
this.currentGroup = gc.group;
this.currentChild = gc.child;
this.childNames = this.currentGroup.getNames();
return true;
} else {
if (this.currentChild == -1) {
return false;
} else {
this.currentChild = -1;
return true;
}
}
}
/**
* Returns true if there is a sibling following the current location.
*/
public boolean hasNextChild() {
if (this.childNames.length > this.currentChild + 1) {
return true;
} else {
return false;
}
}
/**
* Moves to the next sibling of the current location.
*/
public void nextChild() throws HL7Exception {
int child = this.currentChild + 1;
toChild(child);
}
/**
* Moves to the sibling of the current location at the specified index.
*/
public void toChild(int child) throws HL7Exception {
if (child >= 0 && child < this.childNames.length) {
this.currentChild = child;
} else {
throw new HL7Exception("Can't advance to child " + child + " -- only " + this.childNames.length + " children",
HL7Exception.APPLICATION_INTERNAL_ERROR);
}
}
/** Resets the location to the beginning of the tree (the root) */
public void reset() {
this.ancestors = new Stack();
this.currentGroup = root;
this.currentChild = -1;
this.childNames = currentGroup.getNames();
}
/**
* Returns the given rep of the structure at the current location.
* If at root, always returns the root (the rep is ignored).
*/
public Structure getCurrentStructure(int rep) throws HL7Exception {
Structure ret = null;
if (this.currentChild != -1) {
String childName = this.childNames[this.currentChild];
ret = this.currentGroup.get(childName, rep);
} else {
ret = this.currentGroup;
}
return ret;
}
/**
* Returns the group within which the pointer is currently located.
* If at the root, the root is returned.
*/
public Group getCurrentGroup() {
return this.currentGroup;
}
/**
* Returns the array of structures at the current location.
* Throws an exception if pointer is at root.
*/
public Structure[] getCurrentChildReps() throws HL7Exception {
if (this.currentGroup == this.root && this.currentChild == -1)
throw new HL7Exception("Pointer is at root of navigator: there is no current child");
String childName = this.childNames[this.currentChild];
return this.currentGroup.getAll(childName);
}
/**
* Iterates through the message tree to the next segment/group location (regardless
* of whether an instance of the segment exists). If the end of the tree is
* reached, starts over at the root. Only enters the first repetition of a
* repeating group -- explicit navigation (using the drill...() methods) is
* necessary to get to subsequent reps.
* @param segmentsOnly if true, only stops at segments (not groups)
* @param loop if true, loops back to beginning when end of msg reached; if false,
* throws HL7Exception if end of msg reached
*/
public void iterate(boolean segmentsOnly, boolean loop) throws HL7Exception {
Structure start = null;
if (this.currentChild == -1) {
start = this.currentGroup;
} else {
start = (this.currentGroup.get(this.childNames[this.currentChild]));
}
//using a non-existent direction and not allowing segment creation means that only
//the first rep of anything is traversed.
Iterator it = new MessageIterator(start, "doesn't exist", false);
if (segmentsOnly) {
FilterIterator.Predicate predicate = new FilterIterator.Predicate() {
public boolean evaluate(Object obj) {
if (Segment.class.isAssignableFrom(obj.getClass())) {
return true;
} else {
return false;
}
}
};
it = new FilterIterator(it, predicate);
}
if (it.hasNext()) {
Structure next = (Structure) it.next();
drillHere(next);
} else if (loop) {
this.reset();
} else {
throw new HL7Exception("End of message reached while iterating without loop",
HL7Exception.APPLICATION_INTERNAL_ERROR);
}
}
/**
* Navigates to a specific location in the message
*/
private void drillHere(Structure destination) throws HL7Exception {
Structure pathElem = destination;
Stack pathStack = new Stack();
Stack indexStack = new Stack();
do {
MessageIterator.Index index = MessageIterator.getIndex(pathElem.getParent(), pathElem);
indexStack.push(index);
pathElem = pathElem.getParent();
pathStack.push(pathElem);
} while (!root.equals(pathElem) && !Message.class.isAssignableFrom(pathElem.getClass()));
if (!root.equals(pathElem)) {
throw new HL7Exception("The destination provided is not under the root of this navigator");
}
this.reset();
while (!pathStack.isEmpty()) {
Group parent = (Group) pathStack.pop();
MessageIterator.Index index = (MessageIterator.Index) indexStack.pop();
int child = search(parent.getNames(), index.name);
if (!pathStack.isEmpty()) {
this.drillDown(child, 0);
} else {
this.toChild(child);
}
}
}
/** Like Arrays.binarySearch, only probably slower and doesn't require
* a sorted list. Also just returns -1 if item isn't found. */
private int search(Object[] list, Object item) {
int found = -1;
for (int i = 0; i < list.length && found == -1; i++) {
if (list[i].equals(item)) found = i;
}
return found;
}
/**
* Drills down recursively until a segment is reached.
*/
private void findLeaf() throws HL7Exception {
if (this.currentChild == -1)
this.currentChild = 0;
Class c = this.currentGroup.getClass(this.childNames[this.currentChild]);
if (Group.class.isAssignableFrom(c)) {
drillDown(this.currentChild, 0);
findLeaf();
}
}
/**
* A structure to hold current location information at
* one level of the message tree. A stack of these
* identifies the current location completely.
*/
private class GroupContext {
public Group group;
public int child;
public GroupContext(Group g, int c) {
group = g;
child = c;
}
}
}