package com.openMap1.mapper.health.v3;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.w3c.dom.Element;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.util.FileUtil;
import com.openMap1.mapper.util.GenUtil;
import com.openMap1.mapper.util.ModelUtil;
import com.openMap1.mapper.util.XMLUtil;
import com.openMap1.mapper.views.WorkBenchUtil;
/**
* represents one V3 RMIM, which may be either a top RMIM or a CMET
*
* @author robert
*
*/
public class V3RMIM {
private String rmimId; // e.g. 'PORX_HD980030UV'
public String rmimId() {return rmimId;}
private String rmimTrail;// the trail of RMIM ids leading from the top RMIM to this one
private RMIMReader rmimReader;
private String path;
private Element rootElement;
private EPackage RMIMPackage;
private Hashtable<String,V3Name> v3Names;
public V3Name getV3Name(String name) {return v3Names.get(name);}
/**
* @return name of the entry class or choice
*/
public String entryClassName()
{
String name = entryClassNameInMIF;
if (replacedEntryClassName != null)
{
name = replacedEntryClassName;
}
return name;
}
private String entryClassNameInMIF; // name of the entry class or choice
private String replacedEntryClassName = null;
private String expectedOriginalEntryClassName = null;
public V3Name getEntryV3Name()
{
return v3Names.get(entryClassName());
}
private boolean isTopRMIM;
//-------------------------------------------------------------------------------------------
// constructor
//-------------------------------------------------------------------------------------------
/**
* @param rmimReader the reader which is reading in the current MIF file
* @param rootElement the root element of the current MIF file
* @param path file path to the current MIF file
* @param RMIMtrail names of CMETs from the root RMIM down to this RMIM/CMET
* @param keepTopCnoice; if true, make an EClass object for the top Choice
*/
public V3RMIM(RMIMReader rmimReader, Element rootElement, String path, String rmimTrail, boolean isTopRMIM)
throws MapperException
{
this.path = path;
this.rootElement = rootElement;
this.rmimReader = rmimReader;
this.isTopRMIM = isTopRMIM;
if (this.isTopRMIM) {} // seems not to be used
v3Names = new Hashtable<String,V3Name>();
if (checkHeader(rmimTrail,isTopRMIM))
readClasses();
else throw new MapperException("Stopped creation of Ecore model");
}
private boolean checkHeader(String rmimTrail, boolean isTopRMIM)
throws MapperException
{
boolean checked = false;
String uri = rootElement.getNamespaceURI();
boolean headerURIChecks = ((uri != null) && (uri.equals(rmimReader.mifNamespaceURI())));
// skip the URI check for NHS MIM (has MIF 2.0 URI; otherwise like MIF 2.1)
if (rmimReader.isNHSMIF()) headerURIChecks = true;
if (headerURIChecks)
{
if (!rootElement.getLocalName().equals("staticModel"))
throw new MapperException
("Root element of file at " + path + " is not 'staticModel'");
if (!rootElement.getAttribute("representationKind").equals("flat"))
throw new MapperException
("File at " + path + " is not a flat MIF file");
readPackageElement();
Element entryEl = XMLUtil.firstNamedChild(rootElement,"entryPoint");
if ((entryEl == null) && (rmimReader.isNHSMIF())) entryEl = XMLUtil.firstNamedChild(rootElement,"ownedEntryPoint");
if (entryEl == null)
throw new MapperException ("File at " + path + " has no 'entryPoint' element");
String entryName = entryEl.getAttribute("name");
rmimId = entryEl.getAttribute("id");
/* the top RMIM should have an id; otherwise, ask the user if he is prepared
* to use its name as an id */
if (isTopRMIM && (rmimId.equals("")))
{
boolean confirm = WorkBenchUtil.askConfirm("RMIM has no id", "The chosen RMIM has no id on its entry point." +
" Use its name '" + entryName + "' in stead, for the Ecore file name?");
if (!confirm) return false;
else rmimId = entryName;
}
this.rmimTrail = rmimTrail + "/" + rmimId;
RMIMPackage = EcoreFactory.eINSTANCE.createEPackage();
RMIMPackage.setName(rmimId);
entryClassNameInMIF = entryEl.getAttribute("className");
if (rmimReader.isNHSMIF())
{
expectedOriginalEntryClassName = rmimReader.getOriginalNHSEntryClassName(rmimId);
replacedEntryClassName = rmimReader.getAlteredNHSEntryClassName(rmimId);
if ((expectedOriginalEntryClassName != null) && (!entryClassNameInMIF.equals(expectedOriginalEntryClassName)) )
throw new MapperException("Error in template entry names csv file: the entry class of template '"
+ rmimId + "' is '" + entryClassNameInMIF + "', not '" + expectedOriginalEntryClassName + "'");
}
ModelUtil.addMIFAnnotation(RMIMPackage, "name", entryName);
String description = getMIFAnnotation(entryEl);
if (!description.equals(""))
ModelUtil.addMIFAnnotation(RMIMPackage, "description", description);
rmimReader.topPackage().getESubpackages().add(RMIMPackage);
checked = true;
}
else throw new MapperException
("Root element of file at " + path + " is not in the MIF2 namespace");
return checked;
}
/**
* @param el an Element in a MIF file
* @return the text of a (highly nested) annotation on it, or ""
* if the form of the annotation does not match the nested form
*/
private String getMIFAnnotation(Element el)
{
String description = "";
Element annotations = XMLUtil.firstNamedChild(el, "annotations");
if (annotations != null)
{
Element documentation = XMLUtil.firstNamedChild(annotations, "documentation");
if (documentation != null)
{
Element desc = XMLUtil.firstNamedChild(documentation, "description");
if (desc != null)
{
Element text = XMLUtil.firstNamedChild(desc, "text");
if (text != null) description = XMLUtil.getText(text);
}
}
}
return description;
}
/**
* put an annotation on the entry class or choice of this RMIM, to say that it
* is the entry class of the whole model
*/
public void markEntryClass()
{
if (getEntryV3Name() instanceof ConcreteClass)
{
EClass entryClass = ((ConcreteClass)getEntryV3Name()).eClass();
ModelUtil.addMIFAnnotation(entryClass, "entry", "true");
}
/* If the entry point is a choice, create an artificial EClass for it, and
* give it EReferences to the classes the choice resolves to */
else if (getEntryV3Name() instanceof Choice)
{
EClass entryChoice = EcoreFactory.eINSTANCE.createEClass();
entryChoice.setName(getEntryV3Name().name());
ModelUtil.addMIFAnnotation(entryChoice, "entry", "true");
ModelUtil.addMIFAnnotation(entryChoice, "choice", "true");
RMIMPackage.getEClassifiers().add(entryChoice);
// give the choice an unnamed association to each of the concrete classes it resolves to
for (Iterator<EClass> it = ((Choice)getEntryV3Name()).getAllEClasses().iterator();it.hasNext();)
{
EClass child = it.next();
EReference ref = EcoreFactory.eINSTANCE.createEReference();
ref.setName("");
ref.setEType(child);
ref.setContainment(true);
entryChoice.getEStructuralFeatures().add(ref);
}
}
}
private void readPackageElement() throws MapperException
{
/*
Element packageEl = XMLUtil.firstNamedChild(rootElement,"packageLocation");
if (packageEl == null)
throw new MapperException
("File at " + path + " has no 'packageLocation' element");
String realmNamespace = packageEl.getAttribute("realmNamespace");
String artifact = packageEl.getAttribute("artifact");
String subSection = packageEl.getAttribute("subSection");
String domain = packageEl.getAttribute("domain");
String id = packageEl.getAttribute("id");
*/
}
//--------------------------------------------------------------------------------------------
// Reading classes and properties, choices and CMETs
//--------------------------------------------------------------------------------------------
private void readClasses() throws MapperException
{
for (Iterator<Element> it = XMLUtil.namedChildElements(rootElement,classElementName()).iterator();it.hasNext();)
{
Element contained = it.next();
Element classEl = XMLUtil.firstNamedChild(contained, "class");
if (classEl != null)
{
// concrete classes
boolean isConcrete = classEl.getAttribute("isAbstract").equals("false");
// temporary, to see where templates link in to NHS RMIMs
if ((rmimReader.isNHSMIF()) && (classEl.getAttribute("name").startsWith("Template"))) isConcrete = true;
if (isConcrete)
{
EClass theClass = EcoreFactory.eINSTANCE.createEClass();
String className = classEl.getAttribute("name");
// NHS templates may have concrete class names replaced
if ((replacedEntryClassName != null) && (className.equals(entryClassNameInMIF)))
className = replacedEntryClassName;
recordClassOccurrence(className);
theClass.setName(className);
String RIMClassName = derivedValue(classEl,1,"className");
ModelUtil.addMIFAnnotation(theClass,"RIM Class",RIMClassName);
warnIfDuplicateClass(RMIMPackage,className,rmimId);
RMIMPackage.getEClassifiers().add(theClass);
addAttributes(theClass,classEl,RIMClassName);
ConcreteClass conClass = new ConcreteClass(className,theClass);
v3Names.put(className, conClass);
// if the class may import NHS template RMIMs, mark its V3Name and read them
if (rmimReader.isNHSMIF()) readNHSTemplateRMIMs(classEl, conClass);
}
// choice elements
else if (classEl.getAttribute("isAbstract").equals("true"))
{
String choiceName = classEl.getAttribute("name");
// NHS templates may have choice class names replaced (no use case yet)
if ((replacedEntryClassName != null) && (choiceName.equals(entryClassNameInMIF)))
choiceName = replacedEntryClassName;
Choice theChoice = new Choice(choiceName,this);
v3Names.put(choiceName, theChoice);
for (Iterator<Element> ic = XMLUtil.namedChildElements(classEl,choiceChildClassElementName()).iterator();ic.hasNext();)
theChoice.addItem(ic.next().getAttribute(choiceClassNameAttribute()));
}
}
Element cmetEl = XMLUtil.firstNamedChild(contained, "commonModelElementRef");
// store all referenced CMETs in the RMIMReader
if (cmetEl != null)
{
String CMETName = cmetEl.getAttribute(cmetNameAttribute());
// only read any CMET once
if (rmimReader.startedCMETs().get(CMETName) == null)
{
rmimReader.startedCMETs().put(CMETName, "1");
String CMETFileName = rmimReader.getCMETFilename(CMETName);
String CMETFilePath = FileUtil.siblingFilePath(path, CMETFileName);
Element CMETRoot = null;
try {CMETRoot = XMLUtil.getRootElement(CMETFilePath);}
catch (Exception ex) {CMETRoot = null;}
if (CMETRoot != null)
{
boolean isTop = false;
V3RMIM theCMET = new V3RMIM(rmimReader,CMETRoot,CMETFilePath,rmimTrail,isTop);
rmimReader.referencedCMETs().put(CMETName, theCMET);
}
else if (CMETRoot == null) rmimReader.missingCMETs().put(CMETName, "1");
}
}
}
// mark the entry class of the template (NHS MIFs only)
if ((getEntryV3Name() instanceof ConcreteClass) && (rmimReader.isNHSMIF()))
{
EClass entryClass = ((ConcreteClass)getEntryV3Name()).eClass();
ModelUtil.addMIFAnnotation(entryClass, "templateEntry", "true");
}
//writeV3Names();
}
/**
*
* @param thePackage
* @param className
* @param fromPackage
*/
private void warnIfDuplicateClass(EPackage thePackage, String className, String fromPackage) throws MapperException
{
EClass existingClass = (EClass)thePackage.getEClassifier(className);
if (existingClass != null)
{
String message = "Duplicate class '" + className + "' from CMET '"
+ fromPackage + "' will be added to package '" + thePackage.getName() + "'." ;
boolean carryOn = WorkBenchUtil.askConfirm("Warning", message + " Do you want to carry on?");
System.out.println(message);
if (!carryOn) throw new MapperException("User cancelled because: " + message);
}
}
/**
* Look up the template RMIMs allowed by a constraint; mark the V3Name for the class
* as being invoked by the class; and recurse to read in the template RMIMs
* @param classEl
* @param conClass
*/
private void readNHSTemplateRMIMs(Element classEl, ConcreteClass conClass) throws MapperException
{
//String cName = conClass.name();
String constraint = getNHSConstraint(classEl);
if (constraint != null)
{
// record the templateIds allowed by the constraint; there should be some
Vector<String> templateIds = new Vector<String>();
boolean idsFound = false;
/* try all domains which are in the template config file,
* and pick the first that has any template ids for the RMIM. */
for (Enumeration<String> en = rmimReader.NHSDomains().keys(); en.hasMoreElements();)
{
String domainId = en.nextElement();
if (templateIds.size() == 0)
{
idsFound = true;
templateIds = rmimReader.getTemplateIds(constraint, domainId, rmimId);
}
}
if (!idsFound) throw new MapperException("No suggested domain ids found in template config file");
if (templateIds. size() == 0) throw new MapperException("No template ids found in template config file for constraint '"
+ constraint + "' in any domain for RMIM '" + rmimId + "'");
conClass.setTemplateIds(templateIds);
// read the template RMIMs
for (Iterator<String> it = conClass.templateNames().iterator();it.hasNext();)
{
String templateName = it.next();
// only read any CMET once
if (rmimReader.startedCMETs().get(templateName) == null)
{
rmimReader.startedCMETs().put(templateName, "1");
String CMETFileName = rmimReader.getTemplateFilename(templateName);
String CMETFilePath = FileUtil.siblingFilePath(path, CMETFileName);
Element CMETRoot = null;
try {CMETRoot = XMLUtil.getRootElement(CMETFilePath);}
catch (Exception ex)
{
CMETRoot = null; // the lack of the MIF file is noted later
}
if (CMETRoot != null)
{
boolean isTop = false;
V3RMIM theCMET = new V3RMIM(rmimReader,CMETRoot,CMETFilePath,rmimTrail,isTop);
rmimReader.referencedCMETs().put(templateName, theCMET);
}
else if (CMETRoot == null) rmimReader.missingCMETs().put(templateName, "1");
}
// use the CMET, whether you have just made it or not
V3RMIM theCMET = rmimReader.referencedCMETs().get(templateName);
if (theCMET != null)
{
String CMETId = theCMET.rmimId;
String cloneNameSuffix = theCMET.entryClassName();
conClass.addTemplateNameSuffix(templateName, cloneNameSuffix);
conClass.addTemplateClone(templateName, templateCloneClass(conClass.eClass(),cloneNameSuffix,classEl,CMETId));
}
}
}
}
/**
*
* @param plainClass
* @param cloneNameSuffix
* @param classEl
* @return a clone of a class, made from the same Element, but with a suffix after the class name
* @throws MapperException
*/
private EClass templateCloneClass(EClass plainClass,String cloneNameSuffix, Element classEl, String CMETId) throws MapperException
{
EClass theClass = EcoreFactory.eINSTANCE.createEClass();
String className = plainClass.getName() + "_" + cloneNameSuffix;
recordClassOccurrence(className);
theClass.setName(className);
String RIMClassName = derivedValue(classEl,1,"className");
ModelUtil.addMIFAnnotation(theClass,"RIM Class",RIMClassName);
warnIfDuplicateClass(RMIMPackage,className,CMETId);
RMIMPackage.getEClassifiers().add(theClass);
addAttributes(theClass,classEl,RIMClassName);
return theClass;
}
/**
* If a class is marked with an NHS constraint id in a MIF file, return the constraint id
* @param classEl
* @return
*/
private String getNHSConstraint(Element classEl)
{
String constraint = null;
Element annot = XMLUtil.firstNamedChild(classEl, "annotations");
if (annot != null)
{
Vector<Element> consEls = XMLUtil.namedChildElements(annot, "constraint");
for (Iterator<Element> it = consEls.iterator();it.hasNext();)
{
Element cons = it.next();
if (cons.getAttribute("name").equals("contentId"))
{
Element textEl = XMLUtil.firstNamedChild(cons, "text");
Element para = XMLUtil.firstNamedChild(textEl, "p");
if (para != null) constraint = XMLUtil.getText(para);
}
}
}
return constraint;
}
/**
* add the attributes of an RMIM class, as EReferences to the data type class
* @param theClass an RMIM class
* @param classEl the 'class' element representing it in the MIF
* @param RIMClassName the name of the RIM class this is a clone of
* @throws MapperException
*/
private void addAttributes(EClass theClass,Element classEl,String RIMClassName) throws MapperException
{
for (Iterator<Element> it = XMLUtil.namedChildElements(classEl, "attribute").iterator();it.hasNext();)
{
Element attEl = it.next();
String attName = attEl.getAttribute("name");
Element typeEl = XMLUtil.firstNamedChild(attEl, "type");
String typeName = getUnderScoreTypeName(typeEl);
boolean groupingType = isGroupingType(typeName);
if (groupingType) typeName = groupedType(typeName);
String fixedValue = ""; // single fixed value, defined in various ways
// for NHS MIF files, multiple fixed values defined in html for the tabular view
Hashtable<String,String> htmlFixedValues = getHtmlFixedValues(attEl);
if (!rmimReader.isNHSMIF())
{
// can pick up fixed values from a <vocabulary> child of the <attribute>
Element vocabularyEl = XMLUtil.firstNamedChild(attEl, vocabularyElementName());
if (vocabularyEl != null)
{
// two ways of indicating a fixed value
Element codeEl = XMLUtil.firstNamedChild(vocabularyEl, "code");
if (codeEl != null) fixedValue = codeEl.getAttribute("code");
// as found in the May 2009 ballot pack
Element valueSetEl = XMLUtil.firstNamedChild(vocabularyEl, "valueSet");
if (valueSetEl != null) fixedValue = valueSetEl.getAttribute("rootCode");
}
// particularly for the CDA MIF from Jiva, define a fixed value if there is exactly one <enumerationValue> child
Vector<Element> values = XMLUtil.namedChildElements(attEl, "enumerationValue");
if (values.size() == 1) fixedValue = XMLUtil.getText(values.get(0));
}
else if (rmimReader.isNHSMIF())
{
fixedValue=attEl.getAttribute("fixedValue");
}
// RIM structural attributes are attributes of the EClass in the Ecore model
if (V3RIM.isRIMStructuralAttribute(RIMClassName, attName))
{
EAttribute att = EcoreFactory.eINSTANCE.createEAttribute();
att.setName(attName);
att.setEType(getRIMEcoreDataType(attName));
if (attEl.getAttribute("minimumMultiplicity").equals("1")) att.setLowerBound(1);
if (attEl.getAttribute("minimumMultiplicity").equals("0")) att.setLowerBound(0);
if (!fixedValue.equals("")) ModelUtil.addMIFAnnotation(att, "fixed value", fixedValue);
theClass.getEStructuralFeatures().add(att);
}
// Attributes which are not RIM structural attributes are associations to data type classes
else
{
EClass dTypeClass = ModelUtil.getEClass(rmimReader.dataTypePackage(),typeName);
/* if any data type cannot be found, replace it by the type 'ANY'
* (e.g. GTS usually cannot be found; 'ANY' will allow you to use IVL_TS, etc. */
if (dTypeClass == null)
{
rmimReader.missingDataTypes().put(typeName, rmimId);
dTypeClass = ModelUtil.getEClass(rmimReader.dataTypePackage(),"ANY");
}
if (dTypeClass != null) // should now always be non-null
{
EReference ref = EcoreFactory.eINSTANCE.createEReference();
ref.setName(attName);
ref.setEType(dTypeClass);
ref.setContainment(true);
if (!attEl.getAttribute("maximumMultiplicity").equals("1")) ref.setUpperBound(-1);
if (groupingType) ref.setUpperBound(-1);
if (attEl.getAttribute("minimumMultiplicity").equals("1")) ref.setLowerBound(1);
if (attEl.getAttribute("minimumMultiplicity").equals("0")) ref.setLowerBound(0);
/* if a fixed value is put on an EReference to a data type, we do not yet know
* which attribute of the data type it refers to. For II in NHS template RMIMs , it is usually 'extension'
* Use a different annotation key, and decide how to use it later */
if (!fixedValue.equals("")) ModelUtil.addMIFAnnotation(ref, "fixed att value", fixedValue);
theClass.getEStructuralFeatures().add(ref);
// add multiple fixed values of the data type class which have been found in html in the MIF file
for (Enumeration<String> en = htmlFixedValues.keys(); en.hasMoreElements();)
{
String dtAttName = en.nextElement(); // attribute of the data type class
// fixed values along some associations are not to be captured from html
String[] doNotCapture = {"contentId","templateId"};
if (!GenUtil.inArray(ref.getName(), doNotCapture))
{
String fixedVal = htmlFixedValues.get(dtAttName);
String key = "constraint:" + ref.getName() + "/@" + dtAttName;
ModelUtil.addMIFAnnotation(theClass,key,fixedVal);
}
}
}
}
}
}
/**
* find fixed values in NHS MIFS, defined in html for the tabular view like:
<li>The XML attribute <b>code </b>shall contain the value "<font color="#ff0000"><b>OA</b> </font>" </li>
embedded inside an annotation element.
* @param attEl
* @return
*/
private Hashtable<String,String> getHtmlFixedValues(Element attEl)
{
Hashtable<String,String> htmlFixedValues = new Hashtable<String,String>();
Element annotations = XMLUtil.firstNamedChild(attEl,"annotations");
if (annotations != null)
{
Element other = XMLUtil.firstNamedChild(annotations, "otherAnnotation");
if (other != null)
{
Element text = XMLUtil.firstNamedChild(other, "text");
if (text != null)
{
Element listHolder = text;
// there can be 0, 1, or 2 nested <div> elements
Element div1 = XMLUtil.firstNamedChild(text, "div");
if (div1 != null)
{
listHolder = div1;
Element div2 = XMLUtil.firstNamedChild(div1, "div");
if (div2 != null) listHolder = div2;
}
Element ul = XMLUtil.firstNamedChild(listHolder, "ul");
if (ul != null)
{
Vector<Element> vals = XMLUtil.namedChildElements(ul, "li");
for (Iterator<Element> it = vals.iterator(); it.hasNext();)
{
Element li = it.next();
Vector<Element> bolds = XMLUtil.namedChildElements(li, "b");
// <font color="#ff0000"> identifies red text
Vector<Element> reds = XMLUtil.namedChildElements(li, "font");
if ((bolds.size() > 0) && (reds.size() > 0))
{
// attribute name is the first bold text in the lest element
String attName = XMLUtil.getText(bolds.get(0));
// sometimes there is a final space which must be stripped off
if (attName.endsWith(" ")) attName = attName.substring(0, attName.length()-1);
// fixed values are bold inside the <font> element
Element redBold = XMLUtil.firstNamedChild(reds.get(0), "b");
String colour = reds.get(0).getAttribute("color");
if ((redBold != null) && (colour.equals("#ff0000")))
{
String attValue = XMLUtil.getText(redBold);
htmlFixedValues.put(attName,attValue);
}
}
}
}
}
}
}
return htmlFixedValues;
}
/**
*
* @param RIMStructuralAttributeName name of RIM Structural attribute
* @return corresponding ECore data type.
*/
private EDataType getRIMEcoreDataType(String RIMStructuralAttributeName)
{
EDataType aType = EcorePackage.eINSTANCE.getEString();
// negationInd and other 'Ind' are boolean
if (RIMStructuralAttributeName.endsWith("Ind")) aType = EcorePackage.eINSTANCE.getEBoolean();
return aType;
}
/**
*
* @param typeEl
* @return a simple type name like 'II' or a type name like SET_II
*/
private String getUnderScoreTypeName(Element typeEl)
{
String fName = typeEl.getAttribute("name");
for (Iterator<Element> it = XMLUtil.namedChildElements(typeEl, nestedTypeElementName()).iterator();it.hasNext();)
{
Element child = it.next();
fName = fName + "_" + child.getAttribute("name");
}
return fName;
}
/**
* @param typeName
* @return true if this underscored type name is one of the grouping types,
* that refers to multiple occurrences of the grouped type
*/
private boolean isGroupingType(String typeName)
{
boolean group = false;
if (typeName.startsWith("LIST_")) group = true;
if (typeName.startsWith("SET_")) group = true;
if (typeName.startsWith("BAG_")) group = true;
return group;
}
/**
* @param typeName
* @return any part of the name after the first '_'; or the whole name if there is no '_'
*/
private String groupedType(String typeName)
{
String type = typeName;
StringTokenizer st = new StringTokenizer(typeName,"_");
if (st.countTokens() == 2) {st.nextToken(); type = st.nextToken();}
return type;
}
/**
* @param el an Element in a MIF file, which has child elements like
* <mif:derivedFrom staticModelDerivationId="1" className="Role"/>
* with different staticModelDerivationId values
* @param id the integer value (here 1) in 'staticModelDerivationId="1" '
* @param attName the attribute name (here 'className')
* @return the value of the attribute for the element with the given id (here 'Role')
*/
private String derivedValue(Element el, int id, String attName)
{
String dValue = null;
for (Iterator<Element> it = XMLUtil.namedChildElements(el,derivationElementName()).iterator();it.hasNext();)
{
Element derived = it.next();
if (derived.getAttribute("staticModelDerivationId").equals(new Integer(id).toString()))
dValue = derived.getAttribute(attName);
}
return dValue;
}
//----------------------------------------------------------------------------------------------------
// Reading associations - done in a second pass
//----------------------------------------------------------------------------------------------------
/**
* ensure this RMIM knows about all its CMET references, as V3Name objects
*/
public void linkCMETs()
{
//
for (Iterator<Element> it = XMLUtil.namedChildElements(rootElement, classElementName()).iterator();it.hasNext();)
{
Element contained = it.next();
Element cmetEl = XMLUtil.firstNamedChild(contained, "commonModelElementRef");
if (cmetEl != null)
{
String CMETName = cmetEl.getAttribute(cmetNameAttribute());
if (rmimReader.referencedCMETs().get(CMETName) != null)
{
V3RMIM theCMET = rmimReader.referencedCMETs().get(CMETName);
v3Names.put(CMETName, new CMETReference(CMETName, theCMET));
}
}
}
}
@SuppressWarnings("unused")
private void writeV3Names()
{
System.out.println("V3Names in " + rmimId + " with entry class " + entryClassName());
for (Enumeration<V3Name> en = v3Names.elements();en.hasMoreElements();)
{
V3Name vn = en.nextElement();
System.out.println(vn.stringForm());
}
}
/**
* read and store all the associations in this RMIM or CMET
*/
public void readAssociations() throws MapperException
{
trace("Doing associations for " + rmimId);
// if (tracing()) writeV3Names();
for (Iterator<Element> it = XMLUtil.namedChildElements(rootElement,associationElementName()).iterator();it.hasNext();)
{
Element assocEl = it.next();
Element traversable = getTraversableConnection(assocEl);
String roleName = traversable.getAttribute("name");
boolean maxIs1 = true;
String maxMult = traversable.getAttribute("maximumMultiplicity");
// all max multiplicity values like 2, 3 etc. or '*' are treated as infinity
if (!maxMult.equals("1")) maxIs1 = false;
boolean minIs1 = true;
if (traversable.getAttribute("minimumMultiplicity").equals("0")) minIs1 = false;
Element nonTraversable = getNonTraversableConnection(assocEl);
String owningClassName = nonTraversable.getAttribute("participantClassName");
// deal with substitutions of entry class names for NHS CDA templates
if ((owningClassName.equals(entryClassNameInMIF))&& (replacedEntryClassName != null))
owningClassName = replacedEntryClassName;
//trace("Owning class: " + owningClassName + "; Role " + roleName);
V3Name owner = getV3Name(owningClassName);
// the owning V3Name might be a missing CMET
List<ConcreteClass> owningClasses = new Vector<ConcreteClass>();
if (owner != null) owningClasses = owner.getAllConcreteClasses();
else rmimReader.missingClasses().put(owningClassName, rmimId);
if (owningClasses.size() > 0)
{
String endV3Name = traversable.getAttribute("participantClassName");
System.out.println("Outer class: " + owningClassName + " inner: " + endV3Name);
V3Name innerEnd = getV3Name(endV3Name);
if (innerEnd == null) rmimReader.missingClasses().put(endV3Name, rmimId);
else
{
// find the choiceItem elements at the next level
List<Element> choiceEnds = XMLUtil.namedChildElements(traversable, choiceClassElementName());
// no choices; should resolve to one inner end class
if (choiceEnds.size() == 0)
{
if (innerEnd.nItems() == 1)
{
ConcreteClass endClass = null;
if (innerEnd.getAllConcreteClasses().size() == 1) endClass = innerEnd.getAllConcreteClasses().get(0);
storeAssociations(owningClasses,roleName,endClass,minIs1,maxIs1);
}
else throw new MapperException("Association to "
+ innerEnd.name() + " has " + innerEnd.nItems() + " items");
}
if (choiceEnds.size() > 0)
handleChoiceEnds(innerEnd, choiceEnds, owningClasses,minIs1,maxIs1);
}
}
}
}
/**
*
* @param choiceEnds
* @param owningClasses
* @param roleName
* @param maxIs1
*/
private void handleChoiceEnds(V3Name innerEnd, List<Element> choiceEnds, List<ConcreteClass> owningClasses,
boolean minIs1, boolean maxIs1)
throws MapperException
{
// for debugging
@SuppressWarnings("unused")
String owningClassName = "";
if (owningClasses.size() == 1) owningClassName = owningClasses.get(0).eClass().getName();
/* if there is only one choice, then the minimum multiplicity of the association may remain 1.
* if there is more than one choice, they must all have min multiplicity 0. */
boolean trueMinIs1 = minIs1;
if (choiceEnds.size() > 1) trueMinIs1 = false;
for (Iterator<Element> ie = choiceEnds.iterator();ie.hasNext();)
{
Element choiceEnd = ie.next();
String roleName = choiceEnd.getAttribute("traversalName");
String endClassName = choiceEnd.getAttribute("className");
V3Name endV3Name = innerEnd.getNamedChild(endClassName);
if (endV3Name == null) {rmimReader.missingClasses().put(endClassName, rmimId);}
// ignore choices with no choices (abstract classes like 'Template1' in NHS RMIMs)
else if (endV3Name != null)
{
// find the choiceItem elements at the next level
List<Element> nextChoiceEnds = XMLUtil.namedChildElements(choiceEnd, choiceClassElementName());
// no choices; should resolve to one inner end class
if (nextChoiceEnds.size() == 0)
{
if (endV3Name.nItems() == 1)
{
ConcreteClass endClass = null;
// in the next line, 'endV3Name' was 'innerEnd' and I don't understand how it could have worked
if (endV3Name.getAllConcreteClasses().size() == 1) endClass = endV3Name.getAllConcreteClasses().get(0);
storeAssociations(owningClasses,roleName,endClass,trueMinIs1,maxIs1);
}
else System.out.println("Number of inner choices is " + endV3Name.nItems() + " in association to "
+ endClassName + " from class " + owningClasses.get(0).name);
}
else if (nextChoiceEnds.size() > 0)
{handleChoiceEnds(endV3Name, nextChoiceEnds, owningClasses,trueMinIs1,maxIs1); }
}
}
}
@SuppressWarnings("unused")
private boolean emptyChoice(V3Name v3Name)
{
return (v3Name.getAllEClasses().size() == 0);
}
/**
* store associations in the Ecore model of the RMIM, from one or more
* outer (owning) classes to one inner class
* @param owningClasses: Vector of EClasses at the outer end of the association
* @param roleName association role name
* @param endClass EClass at the inner end of the association
* @param isInChoice true if this is inside a choice, so must have lowerBound = 0
*/
private void storeAssociations( List<ConcreteClass> owningClasses,String roleName,ConcreteClass endClass,
boolean minIs1, boolean maxIs1)
throws MapperException
{
for (int i = 0; i < owningClasses.size();i++)
{
ConcreteClass owningConcreteClass = owningClasses.get(i);
EClass owningClass = owningConcreteClass.eClass();
String owningClassName = owningClass.getName();
if (endClass == null) throw new MapperException("Null inner class when storing association from class " + owningClassName);
String innerClassName = endClass.eClass().getName();
boolean outerHasTemplates = (owningConcreteClass.templateNames().size() > 0);
boolean innerHasTemplates = (endClass.templateNames().size() > 0);
if (outerHasTemplates && innerHasTemplates)
throw new MapperException("Outer and inner class both have templates in association from class "
+ owningClassName + " to class " + innerClassName);
// normal case for non-NHS RMIMs; neither outer or inner classes have template clones
if (!outerHasTemplates && !innerHasTemplates)
{
EClass ownedClass = endClass.eClass();
addOneAssociation(owningClass,ownedClass, roleName, null, minIs1,maxIs1);
}
/* NHS RMIMs; case where the inner class has template clones. Make one association
* from the outer class to each template clone, with a special role name */
else if (innerHasTemplates)
{
for (Iterator<String> it = endClass.templateNames().iterator();it.hasNext();)
{
String templateName = it.next();
EClass oneOwnedClass = endClass.getTemplateClone(templateName);
// this role name is introduced by me, and the wrapper transform needs to remove it on the way out
String oneRoleName = roleName + "_" + endClass.getTemplateNameSuffix(templateName);
// if there is more than one template clone class (a choice) each one must have min multiplicity 0
boolean min1 = minIs1;
if (endClass.templateNames().size() > 1) min1 = false;
addOneAssociation(owningClass,oneOwnedClass, oneRoleName, roleName, min1,maxIs1);
}
}
/* NHS RMIMs; case where the outer class has template clones. Make one
* association from each of the template clones of the outer class to each of the the entry classes
* of its template RMIM, with role name as in the NHS templated form */
else if (outerHasTemplates)
for (Iterator<String> iu = owningConcreteClass.templateNames().iterator();iu.hasNext();)
{
String templateName = iu.next();
V3RMIM templateRMIM = rmimReader.getReferencedCMET(templateName);
if (templateRMIM == null) throw new MapperException("Cannot find template RMIM '" + templateName + "'");
EClass oneOwnerClass = owningConcreteClass.getTemplateClone(templateName);
if (oneOwnerClass == null) trace("No template clone class for template '" + templateName
+ "' of class " + owningClassName);
else for (Iterator<EClass> ic = templateRMIM.getEntryV3Name().getAllEClasses().iterator();ic.hasNext();)
{
EClass oneOwnedClass = ic.next();
/* tag name used in NHS templated form has a '.' in it;
* I use it as an association role name to get the same tag name and so keep the wrapper transform simple */
String oneRoleName = templateName + "." + oneOwnedClass.getName();
addOneAssociation(oneOwnerClass,oneOwnedClass, oneRoleName, roleName, minIs1,maxIs1);
}
}
}
}
/**
* add one association to the ecore model - but ensure that there is only one EReference with
* a given role name
* @param owningClass
* @param ownedClass
* @param roleName
* @param originalRoleName if NHS templates have changed the role name,
* record the original name in an annotation
* @param minIs1
* @param maxIs1
*/
private void addOneAssociation(EClass owningClass, EClass ownedClass, String roleName,String originalRoleName,boolean minIs1,boolean maxIs1)
{
if (ModelUtil.getReferencedClass(owningClass, roleName) == null)
{
EReference ref = EcoreFactory.eINSTANCE.createEReference();
ref.setName(roleName);
if (originalRoleName != null)
ModelUtil.addMIFAnnotation(ref, "NHSOriginalRole", originalRoleName);
ref.setEType(ownedClass);
ref.setContainment(true);
if (minIs1) ref.setLowerBound(1); else ref.setLowerBound(0);
if (maxIs1) ref.setUpperBound(1); else ref.setUpperBound(-1);
owningClass.getEStructuralFeatures().add(ref);
}
else
{
String message = "Duplicate association '" + roleName + "' from class '"
+ owningClass.getName() + "' has not been stored.";
WorkBenchUtil.showMessage("Warning", message);
System.out.println(message);
}
}
/**
* record that a class with this name occurs in one or more CMETs
* @param className
*/
private void recordClassOccurrence(String className)
{
Vector<String> occurrences = rmimReader.classOccurrences().get(className);
if (occurrences == null) occurrences = new Vector<String>();
occurrences.add(rmimId);
rmimReader.classOccurrences().put(className, occurrences);
}
/**
* list the packages which a class has occurred in
* @param className
* @return
*/
@SuppressWarnings("unused")
private String packages(String className)
{
String packages = "";
Vector<String> occurrences = rmimReader.classOccurrences().get(className);
if (occurrences != null) for (int i = 0; i < occurrences.size(); i++)
packages = packages + occurrences.get(i) + " ";
return packages;
}
//------------------------------------------------------------------------------------------
// InfrastructureRoot attributes
//------------------------------------------------------------------------------------------
/**
* Add the four infrastructure root attributes to all classes in the RMIM.
* This is done last to make them show last in the RMIM class view.
*/
public void addInfrastructureRootAttributes()
{
for (Iterator<EClassifier> it = RMIMPackage.getEClassifiers().iterator();it.hasNext();)
{
EClassifier ec = it.next();
if (ec instanceof EClass)
{
EClass theClass = (EClass)ec;
// nullFlavor is an attribute in the Ecore model
EAttribute att = EcoreFactory.eINSTANCE.createEAttribute();
att.setName("nullFlavor");
att.setEType(EcorePackage.eINSTANCE.getEString());
att.setLowerBound(0);
theClass.getEStructuralFeatures().add(att);
/* other InfrastructureRoot attributes which are associations to data type classes.
* The NHS MIFs add typeId and templateId explicitly when they want them - don't add them again. */
if (!rmimReader.isNHSMIF())
{
addRootAttribute(theClass,"realmCode","CS",true);
addRootAttribute(theClass,"typeID","II",false);
addRootAttribute(theClass,"templateId","II",true);
}
}
}
}
/**
*
* @param theClass EClass to which the attribute is added
* @param attName name of the infrastructure root attribute
* @param typeName its data type name
* @param canRepeat true if its multiplicity is unbounded
*/
private void addRootAttribute(EClass theClass, String attName, String typeName, boolean canRepeat)
{
EClass dTypeClass = ModelUtil.getEClass(rmimReader.dataTypePackage(),typeName);
if (dTypeClass != null)
{
EReference ref = EcoreFactory.eINSTANCE.createEReference();
ref.setName(attName);
ref.setEType(dTypeClass);
ref.setContainment(true);
if (canRepeat) ref.setUpperBound(-1);
ref.setLowerBound(0);
theClass.getEStructuralFeatures().add(ref);
}
}
//------------------------------------------------------------------------------------------
// differences between MIF versions
//------------------------------------------------------------------------------------------
private String cmetNameAttribute()
{
int version = rmimReader.mifVersion();
if (version == RMIMReader.MIF_2_0) return "name";
if (version == RMIMReader.MIF_2_1) return "name";
if (version == RMIMReader.MIF_2_1_3) return "cmetName";
if (version == RMIMReader.MIF_2_1_4) return "cmetName";
if (version == RMIMReader.MIF_2_1_5) return "cmetName";
if (version == RMIMReader.MIF_2_1_6) return "cmetName";
return "";
}
private String choiceClassElementName()
{
int version = rmimReader.mifVersion();
if (version == RMIMReader.MIF_2_0) return "participantClassSpecialization";
if (rmimReader.isNHSMIF()) return "participantClassSpecialization";
if (version == RMIMReader.MIF_2_1) return "choiceItem";
if (version == RMIMReader.MIF_2_1_3) return "choiceItem";
if (version == RMIMReader.MIF_2_1_4) return "choiceItem";
if (version == RMIMReader.MIF_2_1_5) return "choiceItem";
if (version == RMIMReader.MIF_2_1_6) return "choiceItem";
return "";
}
private String choiceChildClassElementName()
{
if (rmimReader.isNHSMIF()) return "specializationChild";
return "childClass";
}
private String choiceClassNameAttribute()
{
if (rmimReader.isNHSMIF()) return "childClassName";
int version = rmimReader.mifVersion();
if (version == RMIMReader.MIF_2_0) return "className";
if (version == RMIMReader.MIF_2_1) return "name";
if (version == RMIMReader.MIF_2_1_3) return "name";
if (version == RMIMReader.MIF_2_1_4) return "name";
if (version == RMIMReader.MIF_2_1_5) return "name";
if (version == RMIMReader.MIF_2_1_6) return "name";
return "";
}
/**
* @return inner tag name for compound types
*/
private String nestedTypeElementName()
{
if (rmimReader.isNHSMIF()) return "supplierBindingArgumentDatatype";
return "argumentDatatype";
}
private String vocabularyElementName()
{
int version = rmimReader.mifVersion();
if (version == RMIMReader.MIF_2_0) return "supplierVocabulary";
if (version == RMIMReader.MIF_2_1) return "supplierVocabulary";
if (version == RMIMReader.MIF_2_1_3) return "vocabulary";
if (version == RMIMReader.MIF_2_1_4) return "vocabulary";
if (version == RMIMReader.MIF_2_1_5) return "vocabulary";
if (version == RMIMReader.MIF_2_1_6) return "vocabulary";
return "";
}
private Element getTraversableConnection(Element assocEl)
{
int version = rmimReader.mifVersion();
Element traversable = XMLUtil.firstNamedChild(assocEl, "traversableConnection");
// only MIF 2.1 has an intermediate <connections> element
if (version == RMIMReader.MIF_2_1)
{
Element connections = XMLUtil.firstNamedChild(assocEl, "connections");
traversable = XMLUtil.firstNamedChild(connections, "traversableConnection");
}
return traversable;
}
private Element getNonTraversableConnection(Element assocEl)
{
int version = rmimReader.mifVersion();
Element nonTraversable = XMLUtil.firstNamedChild(assocEl, "nonTraversableConnection");
// only MIF 2.1 has an intermediate <connections> element
if (version == RMIMReader.MIF_2_1)
{
Element connections = XMLUtil.firstNamedChild(assocEl, "connections");
nonTraversable = XMLUtil.firstNamedChild(connections, "nonTraversableConnection");
}
return nonTraversable;
}
private String classElementName()
{
String classElementName = "containedClass";
if (rmimReader.isNHSMIF()) classElementName = "ownedClass";
return classElementName;
}
private String associationElementName()
{
String associationElementName = "association";
if (rmimReader.isNHSMIF()) associationElementName = "ownedAssociation";
return associationElementName;
}
private String derivationElementName()
{
String derivationElementName= "derivedFrom";
if (rmimReader.isNHSMIF()) derivationElementName = "derivationSupplier";
return derivationElementName;
}
//---------------------------------------------------------------------------------------------------
// Trivia
//---------------------------------------------------------------------------------------------------
private boolean tracing()
{
return (rmimReader.tracing());
//return ((rmimId != null) && (rmimId.equals("COCD_TP146065UK02")));
}
private void trace(String s) {if (tracing()) System.out.println(s);}
}