package com.openMap1.mapper.health.v3;
import java.io.File;
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.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.URI;
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.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.ui.IWorkbenchPart;
import org.w3c.dom.Element;
import com.openMap1.mapper.actions.MakeITSMappingsAction;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.health.cda.TemplateCollection;
import com.openMap1.mapper.structures.XSDStructure;
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;
import com.openMap1.mapper.AssocEndMapping;
import com.openMap1.mapper.AssocMapping;
import com.openMap1.mapper.AttributeDef;
import com.openMap1.mapper.ElementDef;
import com.openMap1.mapper.GlobalMappingParameters;
import com.openMap1.mapper.ImportMappingSet;
import com.openMap1.mapper.MappedStructure;
import com.openMap1.mapper.MapperFactory;
import com.openMap1.mapper.NodeDef;
import com.openMap1.mapper.NodeMappingSet;
import com.openMap1.mapper.ObjMapping;
import com.openMap1.mapper.ParameterClass;
import com.openMap1.mapper.ParameterClassValue;
import com.openMap1.mapper.PropMapping;
/**
* Class to read a V3 RMIM, and all the CMETs it references directly or indirectly, from
* their MIF files.
*
* @author robert
*
*/
public class RMIMReader {
//-----------------------------------------------------------------------------------------------
// instance variables
//-----------------------------------------------------------------------------------------------
private boolean tracing = false;
public boolean tracing() {return tracing;}
// to support file dialogues
private IWorkbenchPart targetPart;
// name of the wrapper class that implements mappings for the V3 XML ITS
public static String V3JavaMappingClass = "com.openMap1.mapper.converters.V3_XML_ITS";
// name of the wrapper class that converts internal to external CDA form, and back
public static String CDAWrapperClass = "com.openMap1.mapper.converters.CDAConverter";
/**
* @return true if reading a MIF file from the NHS MIM
*/
public boolean isNHSMIF() {return isNHSMIF;}
private boolean isNHSMIF = false; // will be reset if NHS features are spotted, as below
/* NHS MIF file names look like 'COCD_TP147013UK03-BloodPressureRef' and have 13 characters before 'UK'
* or POCD_RM010011GB01_NonCodedCDADocument.mif with 13 characters before 'GB' */
private boolean isNHSMIFName(String mifFileRoot)
{
String realmName = mifFileRoot.substring(13);
return ((realmName.startsWith("UK"))|(realmName.startsWith("GB")));
}
// the root of the TemplateConfig file for NHS MIF files
private Element NHSConfigRoot;
private Element rootElement;
private String mifFilePath;
private String mifFolderPath;
// the root of the top MIF file name is now used as the name of the ecore model
public String mifFileRoot() {return mifFileRoot;}
private String mifFileRoot;
private EPackage topPackage;
public EPackage topPackage() {return topPackage;}
private EPackage dataTypePackage;
public EPackage dataTypePackage() {return dataTypePackage;}
public V3DataTypeHandler v3DataTypeHandler() {return v3DataTypeHandler;}
private V3DataTypeHandler v3DataTypeHandler;
public V3RMIM topRMIM() {return topRMIM;}
private V3RMIM topRMIM;
public String rmimName() {return topRMIM.rmimId();}
private Hashtable<String,String> CMETFileNames = new Hashtable<String,String>();
private Hashtable<String,String> CMETEntryClasses = new Hashtable<String,String>();
private String projectName;
private IFolder v3RMIMsFolder;
public static String DATATYPE_PACKAGE_NAME = "datatypes";
private boolean makeDataTypeMappings = true;
public static String VOCABULARYNAMESPACEURI = "urn:hl7-org:v3/voc";
public static String VOCABULARYNAMESPACEPREFIX = "voc";
public static String CDAPREFIX = "cda";
private boolean CMETIndexRead = false;
// true if the index of CMETs has been read, so it know where CMETs are
public boolean CMETIndexRead() {return CMETIndexRead;}
/** three-letter codes and long names for domains in the NHS template config file */
public Hashtable<String,String> NHSDomains() {return NHSDomains;}
private Hashtable<String,String> NHSDomains = new Hashtable<String,String>();
/**
* First key = mapping set name ( = type name).
* Second key = concatenated class name and package name
* Integer = 1, 2, etc to make the next allocated subset 's1', 's2', etc.
*/
private Hashtable<String,Hashtable<String,Integer>> subsetTable
= new Hashtable<String,Hashtable<String,Integer>>();
/**
*
* @return record of the CMETs in which one class occurs
* key class name; value = Vector of CMET names
*/
public Hashtable<String,Vector<String>> classOccurrences() {return classOccurrences;};
private Hashtable<String,Vector<String>> classOccurrences;
/**
* @return classes referenced in an association but not defined.
* key = class name; value = an RMIM in which the problem was detected
*/
public Hashtable<String,String> missingClasses() {return missingClasses;}
private Hashtable<String,String> missingClasses;
/**
* @return data types used but not defined in the data types MIF.
* key = data type name; value = an RMIM in which the problem was detected
*/
public Hashtable<String,String> missingDataTypes() {return missingDataTypes;}
private Hashtable<String,String> missingDataTypes;
/**
* @return CMETs referenced but not found in a MIF file
* key = CMET name; value = an RMIM in which the problem was detected
*/
public Hashtable<String,String> missingCMETs() {return missingCMETs;}
private Hashtable<String,String> missingCMETs;
/**
* @return CMETs which are already being read and analysed, to avoid
* reading any CMET more than once.
* key = CMET name; value = "1"
*/
public Hashtable<String,String> startedCMETs() {return startedCMETs;}
private Hashtable<String,String> startedCMETs;
/**
* CMETs that have been read and analysed.
* key = CMET name; value = RMIM analysis class instance
*/
public Hashtable<String,V3RMIM> referencedCMETs() {return referencedCMETs;}
private Hashtable<String,V3RMIM> referencedCMETs;
/**
* @param CMETName a referenced CMET
* @return the V3RMIM which analyses it
*/
public V3RMIM getReferencedCMET(String CMETName)
{return referencedCMETs.get(CMETName);}
/**
* V3 data types which are represented in the Ecore model as EAttributes,
* rather than as associations (EReferences) to the data type class
* [0] = V3 type name, [1] = Ecore data type name
*/
private static String[][] EcoreTypes = {{"ST","EString"},{"INT","EInt"}};
/**
* @param V3DataType the name of a V3 data type
* @return if the V3 data type is to be represented in the Ecore model as
* an EAttribute of some Ecore type, return the name of that type;
* otherwise return null
*/
public static String EcoreDataTypeName(String V3DataType)
{
String ecoreType = null;
for (int i = 0; i < EcoreTypes.length; i++)
{
String[] typePair = EcoreTypes[i];
if (typePair[0].equals(V3DataType)) ecoreType = typePair[1];
}
return ecoreType;
}
/**
* @param dataTypeName the name of an Ecore data type
* @return the meta object for that type
*/
public static EDataType getEcoreDataType(String dataTypeName)
{
if (dataTypeName.equals("EString")) return EcorePackage.eINSTANCE.getEString();
if (dataTypeName.equals("EInt")) return EcorePackage.eINSTANCE.getEInt();
if (dataTypeName.equals("EBigDecimal"))return EcorePackage.eINSTANCE.getEBigDecimal();
return null;
}
/**
* paths to '.ent' files which are the top templates to be applied to a CDA MIF
*/
// private Vector<String> topTemplatePaths;
//---------------------------------------------------------------------------------------------------
// Constructor - reads the MIF file and makes the Ecore model
//---------------------------------------------------------------------------------------------------
/**
*
* @param rootElement
* @param mifFilePath
* @param projectName
* @param templateUsageRoot
* @param useJavaMappings
* @param targetPart
* @throws MapperException
*/
public RMIMReader(Element rootElement, XSDStructure xsd, String mifFilePath, String projectName,
Element templateUsageRoot, boolean useJavaMappings, IWorkbenchPart targetPart)
throws MapperException
{
this.mifFilePath = mifFilePath;
this.rootElement = rootElement;
this.projectName = projectName;
this.targetPart = targetPart;
String mifFileName = FileUtil.getFileName(mifFilePath);
StringTokenizer st = new StringTokenizer(mifFileName, ".");
mifFileRoot = st.nextToken();
String ecoreFolderPath = "platform:/resource/" + projectName + "/ClassModel/";
String ecoreFilePath = ecoreFolderPath + mifFileRoot + ".ecore";
int folderLen = mifFilePath.length() - mifFileName.length() - 1;
mifFolderPath = mifFilePath.substring(0, folderLen);
boolean isCDA = isCDAMIFName(mifFilePath);
if (isCDA && (templateUsageRoot == null))
throw new MapperException("A CDA MIF file can only be read in conjunction with a template usage file and some template files");
isNHSMIF = isNHSMIFName(mifFileRoot);
if (isNHSMIF) trace("Reading NHS-style CDA");
topPackage = EcoreFactory.eINSTANCE.createEPackage();
topPackage.setName(packageName(mifFilePath));
// ensure this class model will be viewed as an RMIM in the class model view
ModelUtil.addMIFAnnotation(topPackage, "RMIM", "true");
startedCMETs = new Hashtable<String,String>();
referencedCMETs = new Hashtable<String,V3RMIM>();
classOccurrences = new Hashtable<String,Vector<String>>();
missingCMETs = new Hashtable<String,String>();
missingDataTypes = new Hashtable<String,String>();
missingClasses = new Hashtable<String,String>();
// check the MIF version is supported
mifVersionString = rootElement.getAttribute("schemaVersion");
// NHS MIMs approximate to MIF 2.1 in some respects
if (isNHSMIF() && (mifVersionString.equals(""))) mifVersionString = "2.1";
if (!GenUtil.inArray(mifVersionString, supportedMIFVersion))
throw new MapperException("MIF version '" + mifVersionString + "' is not supported.");
trace("Assumed MIF Version: " + mifVersionString);
// read CMET lookup table
readCMETLookupTable();
trace("Read " + CMETFileNames.size() + " CMET file lookups");
// read entry class name conversions for NHS MIFs
if (isNHSMIF())
{
readEntryNameConversionFile(ecoreFolderPath);
}
// read data types
boolean putElementsInV3Namespace = isCDA;
if (isNHSMIF()) putElementsInV3Namespace = true;
v3DataTypeHandler = new V3DataTypeHandler(this,projectName,putElementsInV3Namespace);
dataTypePackage = v3DataTypeHandler.readDataTypeSchema(ecoreFilePath, makeDataTypeMappings);
trace("Read " + dataTypePackage.getEClassifiers().size() + " data types");
// recursive descent, creating each CMET once only and storing it in referencedCMETs()
String rmimTrail = "";
boolean isTopRMIM = true;
topRMIM = new V3RMIM(this,rootElement,mifFilePath,rmimTrail,isTopRMIM);
trace("Read " + referencedCMETs.size() + " CMETs");
// mark the entry class or choice of the top RMIM as the entry point for the whole model
topRMIM.markEntryClass();
// second pass; ensure each CMET knows about the others it needs, as V3Name objects
topRMIM.linkCMETs();
for (Enumeration<V3RMIM> en = referencedCMETs.elements(); en.hasMoreElements();)
{
V3RMIM cmet = en.nextElement();
cmet.linkCMETs();
}
// third pass; read associations in all CMETs, and add infrastructure root attributes
topRMIM.readAssociations();
topRMIM.addInfrastructureRootAttributes();
for (Enumeration<V3RMIM> en = referencedCMETs.elements(); en.hasMoreElements();)
{
V3RMIM cmet = en.nextElement();
cmet.readAssociations();
cmet.addInfrastructureRootAttributes();
}
trace("Made associations and added infrastructure root attributes");
// record anything that could not be found
writeProblems();
// note how many times each class name occurs
boolean isFalse = false;
if (isFalse) writeClassOccurrences();
// define the top classes for making mapping sets
List<EClass> topClasses = topRMIM.getEntryV3Name().getAllEClasses();
// order EReferences in the Ecore model from the schema, if the user provides one
if (xsd != null) orderFromSchema(topClasses,xsd);
/* read the template files and use them to constrain the Ecore model;
* but NHS CDAs are not done this way (templates are RMIMs; already read) */
if (isCDA && !isNHSMIF)
{
TemplateCollection templateCollection = new TemplateCollection(templateUsageRoot,this);
templateCollection.readTemplateFiles();
templateCollection.resolveAllTemplates();
templateCollection.checkTemplateLevels();
// remake the Ecore model as the constrained model
topPackage = templateCollection.makeConstrainedCDAECoreModel(ecoreFilePath);
/* redefine the list of top classes to make mapping sets
* (the list has only 1 class in it, ClinicalDocument) */
topClasses = templateCollection.topClasses();
trace("Constrained Ecore model");
}
// annotate the entry class to show the NHS wrapper class, if necessary; and the top package
if (isNHSMIF)
{
ModelUtil.addMIFAnnotation(topClasses.get(0), "wrapperClass", MakeITSMappingsAction.NHSV3WrapperClass);
ModelUtil.addMIFAnnotation(topPackage, "isNHSMIF", "true");
}
checkClassesInPackages();
/* for NHS MIFs, ensure there are no ActRelationship or Participation clones with more than one
* Act or Role child classes */
if (isNHSMIF()) normaliseLinkClasses();
// make the Mapping Sets for RMIMs
MIFStructure mifStructure = new MIFStructure(topClasses, topPackage, this);
if (tracing && isFalse) writeMIFStructures(mifStructure);
trace("Making mapping sets: " + mifStructure.allStructures().size());
makeRMIMMappingSets(ecoreFilePath,mifStructure,useJavaMappings,topClasses,isCDA);
}
/**
* checking there are no associations to classes
* not in a package
*/
private void checkClassesInPackages()
{
trace("Model package");
for (Iterator<EPackage> ip = topPackage.getESubpackages().iterator();ip.hasNext();)
{
EPackage subPack = ip.next();
trace("Package " + subPack.getName() + ": " + subPack.getEClassifiers().size() + " classes");
Vector<EClass> classes = new Vector<EClass>();
for (Iterator<EClassifier> it = subPack.getEClassifiers().iterator();it.hasNext();)
{
EClassifier next = it.next();
if (next instanceof EClass) classes.add((EClass)next);
}
for (Iterator<EClass> it = classes.iterator();it.hasNext();)
{
EClassifier next = it.next();
if (next instanceof EClass)
{
EClass theClass = (EClass)next;
for (Iterator<EReference> ir = theClass.getEAllReferences().iterator();ir.hasNext();)
{
EReference ref = ir.next();
EClassifier target = ref.getEType();
if (target == null)
{
theClass.getEStructuralFeatures().remove(ref);
trace("Class " + theClass.getName() + " has association " + ref.getName()
+ " to a null class");
}
else if (target.getEPackage() == null)
{
trace("Class " + theClass.getName() + " has association " + ref.getName()
+ " to class " + target.getName() + " which has no package.");
// try to find a class of the required name in the package of the outer class
EClassifier newTarget = subPack.getEClassifier(target.getName());
if (newTarget != null)
{
ref.setEType(newTarget);
trace("recovered; found class '" + target.getName() + "' in package '" + subPack.getName() + "'");
}
else
{
theClass.getEStructuralFeatures().remove(ref);
trace("cannot recover; class '" + target.getName() + "' not found in package '" + subPack.getName() + "'");
}
}
}
}
}
}
}
/**
* diagnostic write of the ElementDefs in the MIF structure, and their sizes
* @param mifStructure
*/
private void writeMIFStructures(MIFStructure mifStructure)
{
trace("MIF structures");
for (Enumeration<String> en = mifStructure.allStructures().keys();en.hasMoreElements();)
{
String typeName = en.nextElement();
ElementDef elDef = mifStructure.allStructures().get(typeName);
trace("Structure: " + typeName + "; element size: " + mifStructure.getElementSize(elDef));
}
}
/**
* @param path full path to the MIF file
* @return root of the file name followed by '_model', for use as an Ecore package name
*/
private String packageName(String path)
{
StringTokenizer st = new StringTokenizer(FileUtil.getFileName(path),".");
return st.nextToken() + "_model";
}
/**
* read in the directory of CMETs and the data type definition file
* @throws MapperException
*/
private void readCMETLookupTable() throws MapperException
{
if (isNHSMIF())
{
readNHSTemplateConfigFile();
return;
}
String CMETFileName = null;
// up to MIF 2.1, a fixed name for the CMET index file
if ((mifVersionString.equals("2.0"))|(mifVersionString.equals("2.1")))
{
CMETFileName = "cmetList.mif";
}
// MIF 2.1.3 allows different names for the CMET index file
if (mifVersionString.equals("2.1.3"))
{
// index to CMET files
Element CMETFileElement = XMLUtil.firstNamedChild(rootElement, "importedCommonModelElementPackage");
if (CMETFileElement != null) CMETFileName = mifFileName(CMETFileElement);
// fallback which works for the May 2009 Ballot pack
else CMETFileName ="DEFN=UV=IFC=1.8.3.coremif";
}
if (CMETFileName != null)
{
// the CMET index file is in the same folder as all RMIMs
String CMETPath = FileUtil.siblingFilePath(mifFilePath,CMETFileName);
Element CMETFileRoot = XMLUtil.getRootElement(CMETPath);
makeCMETIndex(CMETFileRoot);
CMETIndexRead = true;
}
}
/**
* For NHS MIF files, read the appropriate version of TemplateConfig.xml
*/
private void readNHSTemplateConfigFile() throws MapperException
{
File mifFolder = new File(mifFolderPath);
File[] mifFiles = mifFolder.listFiles();
if (mifFiles == null) throw new MapperException("No folder at '" + mifFolderPath + "'");
for (int f = 0; f < mifFiles.length; f++)
{
File mifFile = mifFiles[f];
String fileName = mifFile.getName();
if (fileName.endsWith("TemplateConfig.xml"))
{
NHSConfigRoot = XMLUtil.getRootElement(mifFile.getAbsolutePath());
// store domain codes from the template config file
Element domainList = XMLUtil.firstNamedChild(NHSConfigRoot, "DomainList");
if (domainList == null) throw new MapperException("Cannot find domain list in template config file '" + fileName + "'");
Vector<Element> domainEls = XMLUtil.namedChildElements(domainList,"domain");
NHSDomains = new Hashtable<String,String>();
for (Iterator<Element> it = domainEls.iterator(); it.hasNext(); )
{
Element domainEl = it.next();
NHSDomains.put(domainEl.getAttribute("id"), XMLUtil.getText(domainEl));
}
}
}
if (NHSConfigRoot == null) throw new MapperException("Cannot find Template Config file");
trace("Found template config file");
}
/**
* find a given template file in the MIF folder.
* This is a mif file whose name begins with the template name.
* Sometimes in a template name like 'POCD_MT010006UK01' , one of 'MT' 'HD' or 'RM'
* needs to be changed to one of the others to get a match. Write a warning if this happens.
* @param templateName
* @return
* @throws MapperException
*/
public String getTemplateFilename(String templateName) throws MapperException
{
String[] name_sections = {"MT","RM", "HD"};
String fName = null;
File mifFolder = new File(mifFolderPath);
File[] mifFiles = mifFolder.listFiles();
if (mifFiles == null) throw new MapperException("No folder at '" + mifFolderPath + "'");
// try matching the file name with no change of 'MT' etc.
for (int f = 0; f < mifFiles.length; f++)
{
String fileName = mifFiles[f].getName();
if (fileName.startsWith(templateName)) fName= fileName;
}
// try matching the file name with some change of 'MT' to 'RM' etc.
for (int i = 0; i < name_sections.length;i++) if (fName == null)
{
String changedTemplateName = changeTemplateName(templateName,name_sections[i]);
trace("Changing template name from '" + templateName + "' to '" + changedTemplateName);
for (int f = 0; f < mifFiles.length; f++)
{
String fileName = mifFiles[f].getName();
if (fileName.startsWith(changedTemplateName)) fName= fileName;
}
}
// still no match found - give up.
if (fName == null) throw new MapperException("Cannot find Template RMIM for '" + templateName + "'");
return fName;
}
/**
* change a template name like 'POCD_MT010006UK01' to 'POCD_RM010006UK01'.
* @param templateName e.g. 'POCD_MT010006UK01'
* @param middleTwoChars e.g. 'RM'
* @return e.g 'POCD_RM010006UK01'
*/
private String changeTemplateName(String templateName, String middleTwoChars)
{
String newName = templateName.substring(0,5) + middleTwoChars + templateName.substring(7,17);
return newName;
}
/**
*
* @param constraintId an NHS constraint id such as 'NPFIT-000014#Role'
* @param domainName a domain identifier such as 'HSC'
* @param parentRMIM the identifier of the parent MIF, such as 'POCD_MT010002UK01'
* @return a list of identifiers for RMIM templates, such as 'COCD_TP145018UK03'
*/
public Vector<String> getTemplateIds(String constraintId, String domainName, String parentRMIM)
{
System.out.println("Constraint " + constraintId + " domain " + domainName + " parent " + parentRMIM);
Vector<String> ids = new Vector<String>();
Vector<Element> templateEls = XMLUtil.namedChildElements(NHSConfigRoot, "Template");
// find the <Template> element with the correct constraint
for (Iterator<Element> it = templateEls.iterator();it.hasNext();)
{
Element tempEl = it.next();
if ((tempEl.getAttribute("id").equals(constraintId)) && (tempEl.getAttribute("status").equals("A")))
{
Element parent = XMLUtil.firstNamedChild(tempEl, "parentModel");
for (Iterator<Element> iu = XMLUtil.namedChildElements(parent, "domain").iterator();iu.hasNext();)
{
Element domain = iu.next();
// find parent RMIM ids for this domain
if (domain.getAttribute("id").equals(domainName))
{
Vector<Element> pidEls = XMLUtil.namedChildElements(domain, "id");
// check that one of the parent ids matches the actual parent RMIM
for (Iterator<Element> ie = pidEls.iterator(); ie.hasNext();)
if (matchable(XMLUtil.getText(ie.next()),(parentRMIM)))
{
Vector<Element> idEls = XMLUtil.namedChildElements(tempEl, "id");
// copy across the template RMIM ids
for (Iterator<Element> ig = idEls.iterator(); ig.hasNext();)
ids.add(XMLUtil.getText(ig.next()));
}
}
}
}
}
System.out.println(GenUtil.singleString(ids));
return ids;
}
/**
*
* @param id1 an RMIM id, like 'POCD_MT010001UK01'
* @param id2 another RMIM id, like 'POCD_RM010001UK01'
* @return true if the two match except for the mismatch of 'RM' and 'MT'
*/
private boolean matchable(String id1, String id2)
{
boolean match = false;
try
{
String p1 = id1.substring(0,4);
String p2 = id2.substring(0,4);
String e1 = id1.substring(7);
String e2 = id2.substring(7);
match = ((p1.equals(p2)) && (e1.equals(e2)));
}
catch (Exception ex) {}
return match;
}
/**
* @param fileNameStart the start of the name of an NHS MIF file
* @return the root element of the file
* @throws MapperException
*/
protected Element getNHSMIFRoot(String fileNameStart) throws MapperException
{
Element root = null;
File mifFolder = new File(mifFolderPath);
File[] mifFiles = mifFolder.listFiles();
if (mifFiles == null) throw new MapperException("No folder at '" + mifFolderPath + "'");
for (int f = 0; f < mifFiles.length; f++)
{
File mifFile = mifFiles[f];
String fileName = mifFile.getName();
if (fileName.startsWith(fileNameStart))
{
root = XMLUtil.getRootElement(mifFile.getAbsolutePath());
}
}
if (root == null) throw new MapperException("Cannot find MIF file with name starting '" + fileNameStart + "'");
return root;
}
/* data types - I am now using the data type schema to define data type classes; but
* retain the following code in case I need to check the version of the data types */
/*
Element dataTypeFileElement = XMLUtil.firstNamedChild(rootElement, "importedDatatypeModelPackage");
if (dataTypeFileElement == null) throw new MapperException("RMIM file has no 'importedDatatypeModelPackage' element");
String dataTypeFileName = mifFileName(dataTypeFileElement);
// the data type MIF file is in the same folder as all RMIMs
String dataTypeFilePath = FileUtil.siblingFilePath(path,dataTypeFileName);
Element dataTypeFileRoot = XMLUtil.getRootElement(dataTypeFilePath);
*/
/**
* @return the entry class for the whole RMIM - not used
*/
public EClass entryClass() throws MapperException
{
List<EClass> topClasses = topRMIM.getEntryV3Name().getAllEClasses();
if (topClasses.size() != 1) throw new MapperException("RMIM does not have one top class");
return topClasses.get(0);
}
//--------------------------------------------------------------------------------------------
// lookup from CMET names to CMET file names
//--------------------------------------------------------------------------------------------
/**
* @param CMETFileRoot the root element of the CMET defining file.
* Set up the index from CMET names to CMET file names
*/
private void makeCMETIndex(Element CMETFileRoot) throws MapperException
{
CMETFileNames = new Hashtable<String,String>();
CMETEntryClasses = new Hashtable<String,String>();
if (!CMETFileRoot.getLocalName().equals("staticModelInterfacePackage"))
throw new MapperException("CMET Index file does not have root element 'staticModelInterfacePackage'");
for (Iterator<Element> it = XMLUtil.namedChildElements(CMETFileRoot, "commonModelElementDefinition").iterator();it.hasNext();)
{
Element el = it.next();
String CMETName = el.getAttribute("name");
Element child = XMLUtil.firstNamedChild(el, "boundStaticModel");
if (child != null)
{
/* In 'COCT_HD010001UV01.mif', 'CO' = subSection, 'CT' = domain,
* '010001' = id, 'UV' = realmNamespace, '01' = version. */
String CMETFileName = child.getAttribute("subSection") + child.getAttribute("domain")
+ "_HD" + child.getAttribute("id") + child.getAttribute("realmNamespace")
+ child.getAttribute("version") + ".mif";
CMETFileNames.put(CMETName, CMETFileName);
}
Element entry = XMLUtil.firstNamedChild(el, "entryClass");
if (entry != null)
{
String entryClassName = entry.getAttribute("name");
CMETEntryClasses.put(CMETName,entryClassName);
}
}
}
/**
* @param CMETName the name of a CMET
* @return the name of the file it is defined in
*/
public String getCMETFilename(String CMETName) throws MapperException
{
if (!CMETIndexRead) throw new MapperException("RMIM file has no 'importedCommonModelElementPackage' element, so cannot look up CMET file names");
return CMETFileNames.get(CMETName);
}
/**
* @param CMETName the name of a CMET
* @return the name of its entry class
*/
public String getCMETEntryClassName(String CMETName)
{return CMETEntryClasses.get(CMETName);}
//--------------------------------------------------------------------------------------------
// miscellaneous
//--------------------------------------------------------------------------------------------
/**
* @return the prefix for the mif namespace used in the top RMIM file
*/
// private String prefix() {return rootElement.lookupPrefix(rootElement.getNamespaceURI()) + ":";}
/**
*
* @param el and element defining the name of a common file - either
* the data types file or the CMET name to file name conversion file
* @return the file name
*/
private String mifFileName(Element el)
{
return (el.getAttribute("root") + "=" + el.getAttribute("realmNamespace")
+ "=" + el.getAttribute("artifact") + "=" + el.getAttribute("version") + ".coremif");
}
/**
* write out the Ecore package as a resource.
*/
public void writePackage(String filePath) throws MapperException
{
ModelUtil.savePackage(filePath, topPackage);
}
/**
* For test purposes, write all problems detected to the system console
*/
private void writeProblems()
{
if (missingCMETs.size() > 0) writeMissingThings("Missing CMETS", missingCMETs, false);
if (missingClasses.size() > 0) writeMissingThings("Missing Classes", missingClasses, true);
if (missingDataTypes.size() > 0) writeMissingThings("Missing Data Types", missingDataTypes, true);
}
private void writeMissingThings(String title, Hashtable<String,String> missingThings, boolean writePlace)
{
System.out.println("\n" + title + ": " + missingThings.size());
for (Enumeration<String> en = missingThings.keys(); en.hasMoreElements();)
{
String thing = en.nextElement();
String place = missingThings.get(thing);
if (writePlace) System.out.println(thing + "\t" + place);
else System.out.println(thing);
}
}
/**
* write out all classes which occur more than once,
* listing the CMETs they occur in
*/
private void writeClassOccurrences()
{
System.out.println("\nClasses which occur more than once:");
int repeats = 0;
for (Enumeration<String> en = classOccurrences.keys();en.hasMoreElements();)
{
String className = en.nextElement();
Vector<String> occs = classOccurrences.get(className);
if (occs.size() > 1)
{
repeats++;
String cmets = className + "(" + occs.size() + ") : \t";
for (int i = 0; i < occs.size(); i++)
cmets = cmets + occs.get(i) + "\t";
System.out.println(cmets);
}
}
System.out.println("\n" + repeats + " repeated classes");
}
//-------------------------------------------------------------------------------------------------
// Making V3-V3 Mapping sets for each RMIM
//-------------------------------------------------------------------------------------------------
/**
* if useJavaMappings = false, make one V3 mapping set for every type definition in the MIF structure, except for those below a minimum
* size (number of ElementDefs)
* if useJavaMappings = true, just make one top Java mapping set for each entry class, which
* invokes the Java mapping class for the V3 XML ITS
*/
private void makeRMIMMappingSets(String ecoreFilePath, MIFStructure mifStructure,
boolean useJavaMappings, List<EClass> topClasses, boolean isCDA)
throws MapperException
{
int mappingSetsMade = 0;
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IProject project = root.getProject(projectName);
IFolder mappingsFolder = project.getFolder("MappingSets");
v3RMIMsFolder = mappingsFolder.getFolder("V3RMIMs");
if (!v3RMIMsFolder.exists())
{
try {v3RMIMsFolder.create(true, true, null);} // force, local, no progress monitor
catch (Exception ex) {throw new MapperException("Cannot make folder for data type mapping sets");}
}
for (Enumeration<String> en = mifStructure.allStructures().keys();en.hasMoreElements();)
{
String typeName = en.nextElement();
ElementDef elementDef = mifStructure.allStructures().get(typeName);
EClass theClass = mifStructure.getEClassForType(typeName);
// if using Java mappings, make only (small) mapping sets for the top classes of the top RMIM
if (useJavaMappings && (isTopClass(theClass,topClasses)))
{
makeMappingSetForRMIM(ecoreFilePath, mifStructure,typeName,elementDef,theClass,useJavaMappings,isCDA);
}
// if not using Java mappings, make mapping sets for all MIF structures
else if (!useJavaMappings)
{
makeMappingSetForRMIM(ecoreFilePath, mifStructure,typeName,elementDef,theClass,useJavaMappings,isCDA);
mappingSetsMade++;
}
}
trace("Mapping sets made: " + mappingSetsMade);
}
/**
*
* @param theClass
* @param topClasses
* @return true if this class is oone of the top classes of the RMIM
*/
private boolean isTopClass(EClass theClass,List<EClass> topClasses)
{
boolean isTop = false;
String cName = theClass.getName();
for (Iterator<EClass> ic = topClasses.iterator(); ic.hasNext();)
if (ic.next().getName().equals(cName)) isTop = true;
return isTop;
}
private void makeMappingSetForRMIM(String ecoreFilePath, MIFStructure mifStructure,
String typeName,ElementDef elementDef,EClass topClass,
boolean useJavaMappings, boolean isCDA)
throws MapperException
{
// do not re-make a mapping set if it exists from a previous run and has not been deleted
IFile existingMappingSet = v3RMIMsFolder.getFile(typeName + ".mapper");
if (!(existingMappingSet.exists()))
{
// (1) Make an empty mapping set in the 'V3RMIMs' folder of the MappingSets folder
String mappingSetLocation = getRMIMMappingSetLocation(projectName,typeName);
URI uri = URI.createURI(mappingSetLocation);
Resource mappingSet = ModelUtil.makeNewMappingSet(uri);
MappedStructure mappedStructure = (MappedStructure)mappingSet.getContents().get(0);
mappedStructure.setUMLModelURL(ecoreFilePath);
// (2) make the MIF structure the structure definition for the mapping set (with its namespaces)
mappedStructure.setStructureURL("");
mappedStructure.setStructureDefinition(mifStructure);
// (3) make the type name the top complex type for the schema
mappedStructure.setTopElementType(typeName);
ElementDef newStructure = mifStructure.typeStructure(typeName);
mappedStructure.setRootElement(newStructure);
// option to make only the top mapping set, which uses Java mappings to implement the V3 XML ITS
if (useJavaMappings)
{
GlobalMappingParameters gmp = mappedStructure.getMappingParameters();
gmp.setMappingClass(V3JavaMappingClass);
// apply the CDA wrapper class if necessary
if (isCDA) gmp.setWrapperClass(CDAWrapperClass);
else if (isNHSMIF()) gmp.setWrapperClass(MakeITSMappingsAction.NHSV3WrapperClass);
}
// option to make all mappings and a load of imported mapping sets
else if (!useJavaMappings)
{
// (4) recursive descent of the structure tree, making all mappings
newStructure.setExpanded(true);
makeMappings(mifStructure,typeName,newStructure, topClass);
// (5) add the correct parameter class to the top node
EClass theClass = mifStructure.getEClassForType(typeName);
addParameterClass(mappedStructure,theClass);
}
// (6) Save the mapping set
ModelUtil.saveMappingSet(mappingSet);
}
}
/**
* @param typeName the name of the mapping set
* @param elementDef a node in the mapped structure
* @param mappedClass the class that is to be mapped to that node
* @return the subset allocated to the mapped class
* make all necessary mappings or imports on this node and its descendants
*/
private String makeMappings(MIFStructure mifStructure,String typeName,ElementDef elementDef, EClass mappedClass)
{
EPackage thePackage = mappedClass.getEPackage();
// make the object mapping for this class
String subset = getSubset(typeName,mappedClass.getName(),thePackage.getName());
addObjectMapping(elementDef,thePackage,mappedClass.getName(),subset);
// make all the property mappings
for (Iterator<AttributeDef> it = elementDef.getAttributeDefs().iterator(); it.hasNext();)
{
AttributeDef attDef = it.next();
addPropertyMapping(attDef,mappedClass,attDef.getName(),subset);
}
// make all object mappings to child classes, and the association mappings to them
for (Iterator<ElementDef> it = elementDef.getChildElements().iterator();it.hasNext();)
{
ElementDef childElement = it.next();
EReference ref = (EReference)mappedClass.getEStructuralFeature(childElement.getName());
EClass childClass = (EClass)ref.getEType();
String importURI = "";
/* if the child node is not expanded, it must either import a mapping set -
* for an CMET or a data type; of if the CMET is too small to have a mapping set, not import it
* but expand it in the structure */
if (!childElement.isExpanded())
{
String childType = childElement.getType();
boolean dataTypeImport = childClass.getEPackage().getName().equals(DATATYPE_PACKAGE_NAME);
// data types always trigger an import of the data type mapping set
if (dataTypeImport)
importURI = getDataTypeMappingSetLocation(projectName,childType);
else if (!dataTypeImport)
importURI = getRMIMMappingSetLocation(projectName,childType);
}
// recursive call to make the object mapping for the child class, and all its descendant mappings
String childSubset = "";
childSubset = makeMappings(mifStructure,typeName,childElement,childClass);
// make the association mapping to the child class
addAssociationMapping(childElement,mappedClass,subset,childClass,childSubset);
// if there is an import mapping set node to make, make it
if (!importURI.equals("")) addImportMappingSet(childElement, importURI, childClass,childSubset);
}
return subset;
}
//-------------------------------------------------------------------------------------------------
// Utility methods for making mapping sets
//-------------------------------------------------------------------------------------------------
/**
* @param projectName the project
* @param typeName the RMIM complex type, which is also the mapping set name
* location of an RMIM mapping set
*/
protected static String getRMIMMappingSetLocation(String projectName,String typeName)
{
return "platform:/resource/" + projectName
+ "/MappingSets/V3RMIMs/" + typeName + ".mapper";
}
/**
* @param projectName the project
* @param typeName the data type name, which is also the mapping set name
* @return location of a data type mapping set
*/
protected static String getDataTypeMappingSetLocation(String projectName,String typeName)
{
return "platform:/resource/" + projectName
+ "/MappingSets/V3DataTypes/" + typeName + ".mapper";
}
/**
* @param mappingSet name of the mapping set
* @param className name of the class for which an object mapping is to be made
* @param package the class is in
* @return a new subset name, to avoid two or more object mappings to the same class
* in the same mapping set, without different subsets.
* The first subset allocated to each class is the default ""
*/
protected String getSubset(String mappingSet, String className, String packageName)
{
String subset = "";
Hashtable<String,Integer> subsetsForMappingSet = subsetTable.get(mappingSet);
if (subsetsForMappingSet == null) subsetsForMappingSet = new Hashtable<String,Integer>();
String cpName = className + "$" + packageName;
Integer subsetIndex = subsetsForMappingSet.get(cpName);
if (subsetIndex == null) subsetIndex = new Integer(1);
else
{
subset = "s" + subsetIndex.intValue();
subsetIndex = new Integer(subsetIndex.intValue() + 1);
}
subsetsForMappingSet.put(cpName, subsetIndex);
subsetTable.put(mappingSet, subsetsForMappingSet);
return subset;
}
/**
* add an object mapping (with its nodeMappingSet) to an ElementDef
* @param node an ElementDef in the mapped structure
* @param className a class name
*/
protected static void addObjectMapping(ElementDef node,EPackage aPackage,String className, String subset)
{
NodeMappingSet nodeSet = MapperFactory.eINSTANCE.createNodeMappingSet();
node.setNodeMappingSet(nodeSet);
ObjMapping om = MapperFactory.eINSTANCE.createObjMapping();
om.setMappedClass(className);
om.setSubset(subset);
om.setMappedPackage(aPackage.getName());
nodeSet.getObjectMappings().add(om);
}
/**
* make a property mapping on a node
* @param nodeDef an ElementDef or AttributeDef node on the mapped structure
* @param aClass the class owning the property to be mapped
* @param propertyName property to be mapped
*/
protected static void addPropertyMapping(NodeDef nodeDef, EClass aClass, String propertyName, String subset)
{
// add a NodeMappingSet to the ElementDef or AttributeDef, if it has not already got one
NodeMappingSet nodeSet = nodeDef.getNodeMappingSet();
if (nodeSet == null)
{
nodeSet = MapperFactory.eINSTANCE.createNodeMappingSet();
nodeDef.setNodeMappingSet(nodeSet);
}
// add a property mapping to the NodeMappingSet
PropMapping pm = MapperFactory.eINSTANCE.createPropMapping();
pm.setMappedClass(aClass.getName());
pm.setSubset(subset);
pm.setMappedProperty(propertyName);
pm.setMappedPackage(aClass.getEPackage().getName());
nodeSet.getPropertyMappings().add(pm);
}
/**
* add an association mapping on a node, which already has a node mapping set.
* The child class is mapped on the same node.
* The association from the parent class (mapped to a higher node) is not navigable back to the parent
* @param child the elementDef which the mapping will be on. Its node name is the association role name
* @param parentClass the parent class
* @param childClass the child class
*/
protected static void addAssociationMapping(ElementDef child, EClass parentClass, String parentSubset,
EClass childClass, String childSubset)
{
NodeMappingSet nodeMappingSet = child.getNodeMappingSet(); // exists because there is an object mapping
AssocMapping am = MapperFactory.eINSTANCE.createAssocMapping();
// the navigable end to the child class is end 2 of the association
AssocEndMapping aem = MapperFactory.eINSTANCE.createAssocEndMapping();
aem.setMappedRole(child.getName());
aem.setMappedClass(childClass.getName());
aem.setSubset(childSubset);
aem.setMappedPackage(childClass.getEPackage().getName());
aem.setRequiredForObject(true);
am.setMappedEnd2(aem);
// the non-navigable end to the parent class is end 1 of the association
AssocEndMapping afm = MapperFactory.eINSTANCE.createAssocEndMapping();
afm.setMappedRole("");
afm.setMappedClass(parentClass.getName());
afm.setSubset(parentSubset);
afm.setMappedPackage(parentClass.getEPackage().getName());
am.setMappedEnd1(afm);
nodeMappingSet.getAssociationMappings().add(am);
}
/**
* add an import mapping set to an ElementDef node
* @param child the node to add the import on
* @param mappingSetURI
* @param rootClass
*/
protected static void addImportMappingSet(ElementDef child,
String mappingSetURI, EClass rootClass, String subset)
{
ImportMappingSet importMappingSet = MapperFactory.eINSTANCE.createImportMappingSet();
importMappingSet.setMappingSetURI(mappingSetURI);
child.setImportMappingSet(importMappingSet);
ParameterClassValue parameterClassValue = MapperFactory.eINSTANCE.createParameterClassValue();
parameterClassValue.setParameterIndex(0);
parameterClassValue.setMappedClass(rootClass.getName());
parameterClassValue.setMappedPackage(rootClass.getEPackage().getName());
parameterClassValue.setSubset(subset);
importMappingSet.getParameterClassValues().add(parameterClassValue);
}
/**
* add a parameter class on the head of a mapping set
* @param mappedStructure
* @param theClass
*/
protected static void addParameterClass(MappedStructure mappedStructure, EClass theClass)
{
ParameterClass parameterClass = MapperFactory.eINSTANCE.createParameterClass();
parameterClass.setClassName(theClass.getName());
parameterClass.setPackageName(theClass.getEPackage().getName());
parameterClass.setParameterIndex(0);
mappedStructure.getParameterClasses().add(parameterClass);
}
//----------------------------------------------------------------------------------------------
// dealing with MIF versions
//----------------------------------------------------------------------------------------------
/**
* return one of the static constants MIF_2_0, MIF_2_1, or MIF_2_1_3
*/
public int mifVersion()
{
int version = 0;
for (int i = 0; i < supportedMIFVersion.length; i++)
if ((mifVersionString != null) && (mifVersionString.equals(supportedMIFVersion[i])))
version = i+1;
return version;
}
public static int MIF_2_0 = 1;
public static int MIF_2_1 = 2;
public static int MIF_2_1_3 = 3;
public static int MIF_2_1_4 = 4;
public static int MIF_2_1_5 = 5;
public static int MIF_2_1_6 = 6;
private String mifVersionString;
/**
* @return version of MIF in use. Currently supports "2.0","2.1","2.1.3 - 6";
*/
public String mifVersionString() {return mifVersionString;}
private String[] supportedMIFVersion = {"2.0","2.1","2.1.3","2.1.4","2.1.5","2.1.6"};
private static String[] mifNamespaceURI = {"urn:hl7-org:v3/mif",
"urn:hl7-org:v3/mif2",
"urn:hl7-org:v3/mif2",
"urn:hl7-org:v3/mif2",
"urn:hl7-org:v3/mif2",
"urn:hl7-org:v3/mif2"};
/**
* @return the MIF namespace URI appropriate to the version of MIF in use
*/
public String mifNamespaceURI()
{
String uri = "";
for (int i = 0; i < supportedMIFVersion.length; i++)
if ((mifVersionString != null) && (mifVersionString.equals(supportedMIFVersion[i])))
uri = mifNamespaceURI[i];
return uri;
}
//---------------------------------------------------------------------------------------------------
// HL7 Clinical Document Architecture (CDA) and templates
//---------------------------------------------------------------------------------------------------
/**
* @param mifFilePath path to a MIF file
* @return true if it is the MIF for a CDA (Clinical Document Architecture)
* which needs to be constrained by templates
*/
public static boolean isCDAMIFName(String mifFilePath)
{
String mifFileName = FileUtil.getFileName(mifFilePath);
return ((mifFileName.startsWith("POCD_HD000040"))|
(mifFileName.startsWith("POCD_MT000040"))|
(mifFileName.startsWith("POCD_RM000040")));
}
//---------------------------------------------------------------------------------------------------
// Order EReferences in the Ecore model from the schema, if the user provides one
//---------------------------------------------------------------------------------------------------
/**
* currently this only operates if there is just one top class, to serve as a starting
* point for descent of the schema
*/
private void orderFromSchema(List<EClass>topClasses,XSDStructure xsd) throws MapperException
{
if (topClasses.size() == 1)
{
EClass rootClass = topClasses.get(0);
String rootName = rootClass.getName();
// check that the root class name is an allowed top element name for the structure
if (!xsd.isTopElementName(rootName))
{showMessage("Cannot order elements","XML Schema does not allow root element name '" + rootName + "'");return;}
// recursive descent of containment eReferences of the Ecore model, ordering them to match the schema
ElementDef topElement = xsd.nameStructure(rootName);
Hashtable<String,EClass> classNames = new Hashtable<String,EClass>();
orderEReferences(rootClass,topElement,xsd,classNames);
}
}
/**
* recursive descent of containment eReferences of the Ecore model, ordering them to match the schema
* This method assumes that all EReferences in the Ecore model are containments.
* @param theClass
* @param theStructure
*/
private void orderEReferences(EClass theClass,ElementDef theElement,
XSDStructure xsd, Hashtable<String,EClass> classNames) throws MapperException
{
String className = theClass.getName();
// avoid infinite recursion, if this class is already being done
if (classNames.get(className) != null) return;
// record that this class is being done, so as not to do it again recursively
classNames.put(className,theClass);
// expand the element definition if necessary
ElementDef expanded= theElement;
if ((!theElement.isExpanded()) && (theElement.getType() != null))
expanded = xsd.typeStructure(theElement.getType());
// set up a new empty EList of EAttributes and EReferences, and copy the EAttributes to it
BasicEList<EStructuralFeature> newFeatures = allAttributes(theClass);
// find the child elements in the correct order for the structure, and build up the new list of EReferences
for (Iterator<ElementDef> it = expanded.getChildElements().iterator();it.hasNext();)
{
ElementDef childEl = it.next();
// strip any namespace prefix from the child node name
String refName= "";
StringTokenizer st = new StringTokenizer(childEl.getName(),":");
while (st.hasMoreTokens()) refName= st.nextToken();
// ignore any lack of infrastructureRoot attributes for the moment
if ((refName.equals("realmCode"))|(refName.equals("templateId"))|(refName.equals("typeId"))) {}
// look for EReferences with the same name, or which had the same name before NHS template treatment
else for (Iterator<EReference> ir = namedReferences(theClass, refName).iterator();ir.hasNext();)
{
EReference ref = ir.next();
newFeatures.add(ref);
// recursive step
EClass childClass = (EClass)ref.getEType();
orderEReferences(childClass,childEl,xsd,classNames);
}
}
// check that all EReferences from the class have been matched by element names in the schema; if not, add unmatched EReferences
checkRefsMatched(theClass,newFeatures);
// apply the new ordered list of EAttributes and EReferences to the class
theClass.eSet(EcorePackage.eINSTANCE.getEClass_EStructuralFeatures(), newFeatures);
}
/**
* check that every EReference in an EClass has been matched by an element name in the schema,
* and included in the new list of structural features; if it has not, include it.
* @param theClass
* @param newFeatures
* @throws MapperException if any EReference has not been matched
*/
private void checkRefsMatched(EClass theClass,BasicEList<EStructuralFeature> newFeatures)
{
for (Iterator<EStructuralFeature> ig = theClass.getEStructuralFeatures().iterator(); ig.hasNext();)
{
EStructuralFeature feat = ig.next();
boolean found = false;
for (Iterator<EStructuralFeature> it = newFeatures.iterator();it.hasNext();)
if (it.next().getName().equals(feat.getName())) found = true;
if (!found)
{
trace("***Did not find feature '" + feat.getName() + "' of class '" + theClass.getName() + "' in the schema.");
newFeatures.add(feat);
}
}
}
/**
* @param theClass
* @return a list of the EAttributes of the EClass
*/
private BasicEList<EStructuralFeature> allAttributes(EClass theClass)
{
BasicEList<EStructuralFeature> newFeatures = new BasicEList<EStructuralFeature>();
for (Iterator<EStructuralFeature> ig = theClass.getEStructuralFeatures().iterator(); ig.hasNext();)
{
EStructuralFeature feat = ig.next();
if (feat instanceof EAttribute) newFeatures.add(feat);
}
return newFeatures;
}
/**
*
* @param theClass
* @param refName
* @return the one Ereference which has the required refName, or a Vector of all those
* EReferences which had that name, before it was changed by NHS template treatment.
*/
private Vector<EReference> namedReferences(EClass theClass, String refName)
{
Vector<EReference> refs = new Vector<EReference>();
EStructuralFeature theFeature = theClass.getEStructuralFeature(refName);
if ((theFeature != null) && (theFeature instanceof EReference)) refs.add((EReference)theFeature);
else if (isNHSMIF())
for (Iterator<EStructuralFeature> it = theClass.getEStructuralFeatures().iterator();it.hasNext();)
{
EStructuralFeature next = it.next();
if ((next instanceof EReference) && (refName.equals(ModelUtil.getMIFAnnotation(next, "NHSOriginalRole"))))
refs.add((EReference)next);
}
if (refs.size() == 0) trace("Missing ref: " + theClass.getName() + "." + refName);
return refs;
}
//---------------------------------------------------------------------------------------------------
// Converting template entry class names, to avoid clashes in class names for NHS templates
//---------------------------------------------------------------------------------------------------
private Hashtable<String,String> alteredNHSEntryClassNames = new Hashtable<String,String>();
private Hashtable<String,String> originalNHSEntryClassNames = new Hashtable<String,String>();
private String entryNameConversionFileName = "TemplateEntryNames.csv";
private String entryConversionColumnNames = "Template,Entry_Name,Altered_Entry_Name";
/**
*
* @param ecoreFolderPath
* @throws MapperException
*/
private void readEntryNameConversionFile(String ecoreFolderPath) throws MapperException
{
String relativeFilePath = ecoreFolderPath + entryNameConversionFileName;
String filePath = FileUtil.absoluteLocation(relativeFilePath);
try
{
Vector<String> fileLines = FileUtil.textLines(filePath);
if (!entryConversionColumnNames.equals(fileLines.get(0)))
throw new MapperException("Columns of template entry conversion csv file should be "
+ entryConversionColumnNames + " but are " + fileLines.get(0));
for (int i = 1; i < fileLines.size(); i++)
{
String[] row = FileUtil.oldParseCSVLine(3, fileLines.get(i));
// template id => original expected entry class name
originalNHSEntryClassNames.put(row[0], row[1]);
// template id => altered entry class name
alteredNHSEntryClassNames.put(row[0], row[2]);
}
}
catch (Exception ex)
{WorkBenchUtil.showMessage("Warning","Could not read template entry class name conversion file at " + filePath);}
}
/**
*
* @param templateId
* @return an altered entry class name for the template, or null if there is no alteration
*/
public String getOriginalNHSEntryClassName(String templateId)
{return originalNHSEntryClassNames.get(templateId);}
/**
*
* @param templateId
* @return an altered entry class name for the template, or null if there is no alteration
*/
public String getAlteredNHSEntryClassName(String templateId)
{return alteredNHSEntryClassNames.get(templateId);}
//---------------------------------------------------------------------------------------------------
// Normalising Link Classes
//---------------------------------------------------------------------------------------------------
/**
* Used for NHS templated CDA models.
* Link Classes are clones of ActRelationships and Participations.
* In a 'normalised' Ecore model, each ActRelationship clone has only one Act clone child, linked to it by a 1..1
* association. Similarly Participations and Role clones.
* In a non-normalised model, an ActRelationship clone may have more than on Act child (a choice), all with
* multiplicity 0..1 and with different templateIds.
* This method converts any non-normalised model to a normalised model, by:
* (1) Splitting the association from the parent class to the link class, giving the split associations
* names ending in '_A', '_B' and so on.
* (2) Splitting the link classes, with the same name suffixes
* (3) each link class has only one 1..1 association to an Act or Role clone.
*/
private void normaliseLinkClasses() throws MapperException
{
trace("Normalising split link classes");
// normalise link classes in all packages under the top package
for (Iterator<EPackage> it = topPackage.getESubpackages().iterator();it.hasNext();)
normaliseLinkClasses(it.next());
}
/**
* normalise all link classes within a package.
* Assumes that the parent class is within the same package as the link class - throws an exception if not so
* @param thePackage
*/
private void normaliseLinkClasses(EPackage thePackage) throws MapperException
{
String[] linkClass = {"ActRelationship","Participation"};
// make a temporary list of classes in the package as you will be modifying it
Vector<EClassifier> allClasses = new Vector<EClassifier>();
for (Iterator<EClassifier> it = thePackage.getEClassifiers().iterator();it.hasNext();) allClasses.add(it.next());
for (Iterator<EClassifier> ix = allClasses.iterator();ix.hasNext();)
{
EClassifier next = ix.next();
if (next instanceof EClass)
{
EClass theClass = (EClass)next;
String rimClass = ModelUtil.getMIFAnnotation(theClass, "RIM Class");
// link classes may be clones of ActRelationship or Participation
for (int linkIndex = 0; linkIndex < 2; linkIndex++)
{
if ((rimClass != null) && (rimClass.equals(linkClass[linkIndex])))
{
Vector<EReference> rimChildRefs = findRimChildRefs(theClass,linkIndex);
// normalise any link class with associations to more than one RIM class
if (rimChildRefs.size() > 1) normaliseOneLinkClass(theClass,rimChildRefs);
}
}
}
}
}
/**
*
* @param theClass an ActRelationship or Participatoin clone class
* @param linkIndex 0 for ActRelationship, 1 for Role
* @return a Vector of associations to child Act or Role clone classes
*/
private Vector<EReference> findRimChildRefs(EClass theClass,int linkIndex)
{
Vector<EReference> rimRefs = new Vector<EReference>();
for (Iterator<EReference> it = theClass.getEAllReferences().iterator();it.hasNext();)
{
EReference ref = it.next();
EClass target = (EClass)ref.getEType();
String targetRimClass = ModelUtil.getMIFAnnotation(target, "RIM Class");
if ((targetRimClass != null) && (GenUtil.inArray(targetRimClass, linkChildClass(linkIndex))))
rimRefs.add(ref);
}
return rimRefs;
}
/**
* normalise one link class which has been found to have more than one association toa RIM child class
* @param theClass
* @param rimChildRefs
*/
private void normaliseOneLinkClass(EClass theClass,Vector<EReference> rimChildRefs) throws MapperException
{
EPackage thePackage = theClass.getEPackage();
int found = 0;
// make a temporary list of classes in the package as you will be modifying it
Vector<EClassifier> allClasses = new Vector<EClassifier>();
for (Iterator<EClassifier> it = thePackage.getEClassifiers().iterator();it.hasNext();) allClasses.add(it.next());
// find the parent class in the same package which has an association to the link class - throw an exception if not found
for (Iterator<EClassifier> iy = allClasses.iterator();iy.hasNext();)
{
EClassifier next = iy.next();
if (next instanceof EClass)
{
EClass parentClass = (EClass)next;
//make a temporary list of references , as you may modify the list
Vector<EReference> refs = new Vector<EReference>();
for (Iterator<EReference> ix = parentClass.getEAllReferences().iterator();ix.hasNext();) refs.add(ix.next());
for (Iterator<EReference> iu = refs.iterator();iu.hasNext();)
{
EReference ref = iu.next();
EClass target = (EClass)ref.getEType();
if (target.getName().equals(theClass.getName()))
{
normaliseALinkClass(parentClass, ref,rimChildRefs);
// the association has been replaced by two or more new ones - so remove it
boolean linkRemoved = parentClass.getEStructuralFeatures().remove(ref);
if (!linkRemoved) throw new MapperException("Failed to remove association to split link class "
+ ref.getName() + " from class " + parentClass.getName());
found++;
}
}
}
}
if (found != 1) throw new MapperException("There is not one parent class of split link class '"
+ theClass.getName() + "' in the same package '" + thePackage.getName() + "'; there are " + found);
// remove the old link class from the package
boolean classRemoved = thePackage.getEClassifiers().remove(theClass);
if (!classRemoved) throw new MapperException("Failed to remove split link class " +theClass.getName() + " from package " + thePackage.getName());
}
/**
*
* @param parentClass
* @param parentRef
* @param rimChildRefs
*/
private void normaliseALinkClass(EClass parentClass,EReference parentRef, Vector<EReference> rimChildRefs) throws MapperException
{
trace("Normalising split link class " + parentRef.getEType().getName() + " in package " + parentClass.getEPackage().getName());
String[] suffixes = {"_A","_B","_C","_D","_E"};
// collect names of associations not to be cloned on the new link classes
Vector<String> refNames = new Vector<String>();
for (Iterator<EReference> it = rimChildRefs.iterator(); it.hasNext();) refNames.add(it.next().getName());
// make one new link class for each association from the old link class to a child
int index = 0;
for (Iterator<EReference> it = rimChildRefs.iterator(); it.hasNext();)
{
EReference childRef = it.next();
makeNewLinkClassAndRef(parentClass, parentRef, childRef,suffixes[index], refNames);
index++;
}
}
/**
*
* @param thePackage
* @param parentClass
* @param parentRef
* @param childRef
* @param index
*/
private void makeNewLinkClassAndRef(EClass parentClass, EReference parentRef, EReference childRef,String suffix,Vector<String> refNames)
{
// make the new link class, with no associations to child RIM classes
EClass oldLinkClass = (EClass)parentRef.getEType();
String newClassName = oldLinkClass.getName() + suffix;
EClass newLinkClass = ModelUtil.cloneEClassWithoutFeatures(oldLinkClass, newClassName, refNames);
// add one association from the new link class to the correct child RIM class
EReference newChildRef = ModelUtil.cloneEReference(childRef);
newChildRef.setLowerBound(1);
newLinkClass.getEStructuralFeatures().add(newChildRef);
// add a renamed association from the parent class to the new link class
EReference newParentRef = EcoreFactory.eINSTANCE.createEReference();
newParentRef.setName(parentRef.getName() + suffix);
newParentRef.setEType(newLinkClass);
newParentRef.setLowerBound(parentRef.getLowerBound());
newParentRef.setUpperBound(parentRef.getUpperBound());
parentClass.getEStructuralFeatures().add(newParentRef);
}
/**
*
* @param linkIndex 0 for ActRelationship, 1 for Participation
* @return a list of possible child classes for the class above
*/
private String[] linkChildClass(int linkIndex)
{
if (linkIndex == 0) return ModelUtil.ActSubclasses;
else return ModelUtil.RoleSubclasses;
}
//---------------------------------------------------------------------------------------------------
// Trivia
//---------------------------------------------------------------------------------------------------
private void trace(String s) {if (tracing) System.out.println(s);}
protected void showMessage(String title, String message) {
MessageDialog.openInformation(
targetPart.getSite().getShell(),
title,
message);
}
}