package com.openMap1.mapper.structures;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import com.openMap1.mapper.converters.V2Converter;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.core.NamespaceSet;
import com.openMap1.mapper.core.namespace;
import com.openMap1.mapper.util.ModelUtil;
import com.openMap1.mapper.util.XMLUtil;
import com.openMap1.mapper.util.GenUtil;
import com.openMap1.mapper.ElementDef;
import com.openMap1.mapper.MapperFactory;
import com.openMap1.mapper.MaxMult;
import com.openMap1.mapper.MinMult;
import org.eclipse.emf.ecore.EObject;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Define the structure for a Mapped structure, getting
* the information from a template file from the
* HL7 V2 Messaging WorkBench (MWB).
*
* ElementDefs in this structure have the following annotations:
*
* key = V2NodeType; possible values = 'Message', 'Segment', 'Field', 'Component', 'SubComponent'
* key = length; value = the integer length as a String
* key = V2DataType; value = V2 data type
* key = HL7Version; value = the V2 version, e.g. 2.5.1
* key = table; value = the numeric identifier of the V2 table, e.g. 0301
*
* @author robert
*
*/
public class V2StructureDef implements StructureDefinition{
private Element mwbRoot;
private Hashtable<String,ElementDef> elementsByName = new Hashtable<String,ElementDef>();
private Hashtable<String,ElementDef> elementsByType = new Hashtable<String,ElementDef>();
private String HL7Version;
private String[] mwbNodeTypes = {"SegGroup","Segment","Field","Component","SubComponent"};
//---------------------------------------------------------------------------------
// Constructor
//---------------------------------------------------------------------------------
public V2StructureDef(Element mwbRoot) throws MapperException
{
this.mwbRoot = mwbRoot;
readMWBFile();
}
//---------------------------------------------------------------------------------
// Reading the Messaging Workbench Template File
//---------------------------------------------------------------------------------
private void readMWBFile() throws MapperException
{
elementsByName = new Hashtable<String,ElementDef>();
elementsByType = new Hashtable<String,ElementDef>();
if (mwbRoot.getTagName().equals("HL7v2xConformanceProfile"))
{
HL7Version = mwbRoot.getAttribute("HL7Version");
Element top = XMLUtil.firstNamedChild(mwbRoot, "HL7v2xStaticDef");
if (top == null)throw new MapperException("Found no 'HL7v2xStaticDef' Element in MWB file");
// define the root Element of the message
ElementDef elDef = MapperFactory.eINSTANCE.createElementDef();
String messageName = top.getAttribute("MsgStructID");
String messageType = messageName + "_Type";
elDef.setName(messageName);
elDef.setType(messageType);
elDef.setMinMultiplicity(MinMult.ONE);
elDef.setMaxMultiplicity(MaxMult.ONE);
elDef.addAnnotation("HL7Version", HL7Version);
// store the top element in Hashtables to support the StructureDefinition interface
elementsByName.put(messageName, elDef);
elementsByType.put(messageType, elDef);
// define the (segment and segment group) children of the root element
for (int i = 0; i < top.getChildNodes().getLength();i++)
{
Node nd = top.getChildNodes().item(i) ;
// position is not used in segment names, so node position = 0 will do
if (isV2Node(nd))
{
// build the subtree of the top node down to the next typed nodes
ElementDef child = readMWBElement(messageName,elDef,(Element)nd,0,false);
elDef.getChildElements().add(child);
// build subtrees below the next typed nodes
readMWBElement(messageName,elDef,(Element)nd,0,true);
}
}
}
else throw new MapperException("Root element of MWB file has wrong tag name '"
+ mwbRoot.getTagName() + "'");
}
/**
* @param node a Node in an MWB template file
* @return true if this is one of the V2 node types
*/
private boolean isV2Node(Node node)
{
return ((node instanceof Element) && (GenUtil.inArray(node.getNodeName(), mwbNodeTypes)));
}
/**
* read an Element of an MWB profile file at any level below the message,
* and define the ElementDef in the structure. Store the ElementDef in Hashtables
* for use by the StructureDef interface
*
* @param messageName: the name o the whole message, eg ADT_A01
* @param parent the parent ElementDef
* @param el the element in the MWB file
* @param nodePos position of this node as a child of its parent
* @param topOfSubtree true if this ElementDef is to be stored in the Hashtables
* as the top of a structure subtree; false if it is part of some other subtree
* @return the ElementDef for this node of the V2 structure
*/
private ElementDef readMWBElement(String messageName,ElementDef parent, Element el, int nodePos, boolean topOfSubtree)
{
ElementDef elDef = MapperFactory.eINSTANCE.createElementDef();
// values 'SegGroup', 'Segment','Field', 'Component', 'SubComponent'
String V2NodeType = el.getTagName();
elDef.addAnnotation("V2NodeType", V2NodeType);
String mwbName = el.getAttribute("Name");
String mwbLongName = el.getAttribute("LongName");
// String mwbUsage = el.getAttribute("Usage");
String mwbTable = el.getAttribute("Table");
if (!mwbTable.equals("")) elDef.addAnnotation("Table", mwbTable);
String mwbLength = el.getAttribute("Length");
if (!mwbLength.equals("")) elDef.addAnnotation("Length", mwbLength);
String mwbDataType = el.getAttribute("Datatype");
if (!mwbDataType.equals(""))
{
elDef.setType(mwbDataType);
// offer no editor option to expand the node if it has no child nodes
if (!hasChildElements(el)) elDef.setExpanded(true);
elDef.addAnnotation("V2DataType", mwbDataType);
}
String mwbMin = el.getAttribute("Min");
if (mwbMin.equals("0")) elDef.setMinMultiplicity(MinMult.ZERO);
if (mwbMin.equals("1")) elDef.setMinMultiplicity(MinMult.ONE);
String mwbMax = el.getAttribute("Max");
if (mwbMax.equals("1")) elDef.setMaxMultiplicity(MaxMult.ONE);
if (mwbMax.equals("*")) elDef.setMaxMultiplicity(MaxMult.UNBOUNDED);
// define the Element name, type, and descriptive long name for the different V2 node types
if (V2NodeType.equals("SegGroup"))
{
//Element names for segment groups are prefixed by the message name
String elementName = messageName + "." + mwbName;
elDef.setName(elementName);
elDef.setType(elementName + "_Type");
elDef.setDescription(mwbLongName);
}
else if (V2NodeType.equals("Segment"))
{
// Element names for segments are the segment names
elDef.setName(mwbName);
elDef.setType(mwbName + "_Type");
elDef.setDescription(mwbLongName);
}
else
{
String parentDataType = parent.getAnnotation("V2DataType");
if (V2NodeType.equals("Field"))
elDef.setName(parent.getName() + "." + nodePos);
else if ((parentDataType != null)&&(!(parentDataType.equals(""))))
elDef.setName(parentDataType + "." + nodePos);
else System.out.println("Cannot name element for " + V2NodeType
+ " '" + mwbName + "'");
elDef.setDescription(mwbName);
}
// define the children of this element
int childPos = 1;
for (int i = 0; i < el.getChildNodes().getLength();i++)
{
Node nd = el.getChildNodes().item(i) ;
if (isV2Node(nd))
{
// build the subtree of the top node down to the next typed nodes
if (topOfSubtree|(!hasType(elDef)))
{
ElementDef child = readMWBElement(messageName,elDef,(Element)nd,childPos,false);
elDef.getChildElements().add(child);
}
// build subtrees below the next typed nodes
readMWBElement(messageName,elDef,(Element)nd,childPos,true);
childPos++;
}
}
// store the element for retrieval in the StructureDef interface
if ((hasType(elDef)) && topOfSubtree)
{
elementsByName.put(elDef.getName(), elDef);
elementsByType.put(elDef.getType(), elDef);
}
return elDef;
}
/**
* @param elDef an ElementDef
* @return true if this ElementDef has a type, and so has an entry
* in the Hashtable of types and stops the tree of some types ancestor ElementDef
*/
private boolean hasType(ElementDef elDef)
{
return ((elDef.getType() != null) && (!elDef.getType().equals("")));
}
/**
* @param el an Element in an MWB file
* @return true if this Element has any child nodes that represent V2 nodes
*/
private boolean hasChildElements(Element el)
{
boolean hasChildren = false;
for (int i = 0; i < el.getChildNodes().getLength();i++)
if (isV2Node(el.getChildNodes().item(i))) hasChildren = true;
return hasChildren;
}
//---------------------------------------------------------------------------------
// Interface StructureDefinition
//---------------------------------------------------------------------------------
/**
* find the Element and Attribute structure of some named top element (which may have a named
* complex type, or a locally defined anonymous type), stopping at the
* next complex type definitions it refers to
* @param String name the name of the element
* @return Element the EObject subtree (Element and Attribute EObjects) defined by the name.
* Make a clone because the Editor may do nasty things to it
*/
public ElementDef nameStructure(String name) throws MapperException
{
ElementDef result = null;
EObject eDef = ModelUtil.EClone(elementsByName.get(name),true);
if (eDef instanceof ElementDef) result = (ElementDef)eDef;
return result;
}
/**
* find the Element and Attribute structure of some complex type, stopping at the
* next complex type definitions it refers to
* @param type the name of the complex type
* @return the EObject subtree (Element and Attribute EObjects) defined by the type
* Make a clone because the Editor may do nasty things to it.
*/
public ElementDef typeStructure(String type) throws MapperException
{
ElementDef result = null;
EObject eDef = ModelUtil.EClone(elementsByType.get(type),true);
if (eDef instanceof ElementDef) result = (ElementDef)eDef;
return result;
}
/**
*
* @return an array of the top-level complex types defined in the structure definition -
* any of which can be the type of a mapping set
*/
public String[] topComplexTypes()
{
ArrayList<String> allTopTypes = new ArrayList<String>();
allTopTypes.add(""); // the default choice on the menu, before any choice is made, is ""
for (Enumeration<String> en = elementsByType.keys(); en.hasMoreElements();)
{
String name = en.nextElement();
StringTokenizer st = new StringTokenizer(name,".");
// exclude any type names with a '.' in them
if (st.countTokens() == 1) allTopTypes.add(name);
}
String[] res = new String[allTopTypes.size()];
return allTopTypes.toArray(res);
}
/**
*
* @return an array of the top-level complex types defined in the structure definition -
* any of which can be the type of a mapping set
*/
public String[] topElementNames()
{
ArrayList<String> allTopNames = new ArrayList<String>();
allTopNames.add(""); // the default choice on the menu, before any choice is made, is ""
for (Enumeration<String> en = elementsByName.keys(); en.hasMoreElements();)
{
String name = en.nextElement();
StringTokenizer st = new StringTokenizer(name,".");
// exclude all names with a '.' in them
if (st.countTokens() == 1) allTopNames.add(name);
}
String[] res = new String[allTopNames.size()];
return allTopNames.toArray(res);
}
/**
* @return the set of namespaces defined for the structure
*/
public NamespaceSet NSSet()
{
NamespaceSet nss = new NamespaceSet();
try {nss.addNamespace(new namespace("",V2Converter.V2_NAMESPACE_URI));}
catch (Exception ex) {}
return nss;
}
//---------------------------------------------------------------------------------
// Interface PropertyValueSupplier
//---------------------------------------------------------------------------------
/**
*
* @param modelClassName
* @param modelPropertyName
* @return true if this property value supplier supplies values for the
* model class and property, for drop-down choices in the editor
*/
public boolean suppliesPropertyValues(String modelClassName, String modelPropertyName)
{
if (modelClassName.equals("MappedStructure"))
{
if (modelPropertyName.equals("Top Element Type")) return true;
if (modelPropertyName.equals("Top Element Name")) return true;
}
return false;
}
/**
*
* @param modelClassName
* @param modelPropertyName
* @return the values supplied by this supplier for the model class and property
*/
public String[] propertyValues(String modelClassName, String modelPropertyName)
{
if (modelClassName.equals("MappedStructure"))
{
if (modelPropertyName.equals("Top Element Type")) return topComplexTypes();
if (modelPropertyName.equals("Top Element Name")) return topElementNames();
}
return new String[0];
}
}