package com.openMap1.mapper.health.v3; import java.util.ArrayList; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.List; 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.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import com.openMap1.mapper.converters.CDAConverter; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.core.NamespaceException; import com.openMap1.mapper.core.NamespaceSet; import com.openMap1.mapper.core.namespace; import com.openMap1.mapper.structures.StructureDefinition; import com.openMap1.mapper.util.GenUtil; import com.openMap1.mapper.util.ModelUtil; import com.openMap1.mapper.util.XMLUtil; import com.openMap1.mapper.AttributeDef; import com.openMap1.mapper.ElementDef; import com.openMap1.mapper.MapperFactory; import com.openMap1.mapper.MaxMult; import com.openMap1.mapper.MinMult; /** * Class to define ElementDef trees from the RMIM class model. * Each ElementDef tree is used to make one of the V3-V3 mapping sets. * @author robert * */ public class MIFStructure implements StructureDefinition { private RMIMReader rmimReader; // the overarching model package, not the package with the top classes in it private EPackage topPackage; // list of top classes; one of them, unless the RMIM has a choice on top private List<EClass> topClasses; /** key = type name; value = the ElementDef structure with that type */ public Hashtable<String,ElementDef> allStructures() {return allStructures;} private Hashtable<String,ElementDef> allStructures; /** key = type name; value = the EClass to be mapped to the ElementDef with that type */ private Hashtable<String,EClass> allEClasses; /** * @param typeName name of a complex type in a MappedStructure * @return the EClass mapped to that node */ public EClass getEClassForType(String typeName) {return allEClasses.get(typeName);} //private int treeSize; private boolean tracing = false; //----------------------------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------------------------- public MIFStructure(List<EClass> topClasses, EPackage topPackage,RMIMReader rmimReader) { this.rmimReader = rmimReader; this.topClasses = topClasses; this.topPackage = topPackage; // the overarching model package, not the package with the top classes in it countReferencesToClasses(this.topPackage); // used to set 'references' which is not used makeAllStructures(); } //----------------------------------------------------------------------------------------------- // Traverse the Ecore model to make all ElementDef structures needed //----------------------------------------------------------------------------------------------- private void makeAllStructures() { allStructures = new Hashtable<String,ElementDef>(); allEClasses = new Hashtable<String,EClass>(); // set up the initial set of type names and EClasses to make structures for for (Iterator<EClass> it = topClasses.iterator();it.hasNext();) { EClass topClass = it.next(); // the type name is the top RMIM name followed by the top class name String typeName = topClass.getEPackage().getName() + "_" + topClass.getName(); allEClasses.put(typeName, topClass); } /* Start making structures for type names. The Hashtable of type names will extend * as you make further structures; but keep on passing through the Hashtable * until there is a structure for every entry */ int pass = 0; while (someStructuresNotMade()) { pass++; trace("Pass " + pass); for (Enumeration<String> en = allEClasses.keys();en.hasMoreElements();) { String typeName = en.nextElement(); EClass theClass = allEClasses.get(typeName); storeElementDef(typeName,theClass); } } } /** * @return true if some type names have been found, * but the ElementDef structures not yet made for them */ private boolean someStructuresNotMade() { boolean someNotMade = false; for (Enumeration<String> en = allEClasses.keys();en.hasMoreElements();) { String typeName = en.nextElement(); if (allStructures.get(typeName) == null) someNotMade = true; } return someNotMade; } /** * Create and store an ElementDef under its type name, if it has not been stored already; * and recursively fill in the structure under it * @param typeName * @param theClass */ private void storeElementDef(String typeName, EClass theClass) { // ensure you don't build any structure more than once if (allStructures.get(typeName) == null) { // make and store the ElementDef ElementDef elementDef = MapperFactory.eINSTANCE.createElementDef(); elementDef.setName(theClass.getName()); elementDef.setType(typeName); allStructures.put(typeName, elementDef); // add AttributeDefs and child ElementDefs recursively down the tree, noting RIM classes makeStructure(elementDef, theClass); // trace("Type " + typeName + " size: " + treeSize); } } /** * @param elementDef an ElementDef whose name and type have been defined * @param theClass the class it represents * fills in the correct structure of child elements and attributes */ public void makeStructure(ElementDef elementDef, EClass theClass) { //treeSize = 0; // to keep track of the number of ElementDefs under this one Vector<String> rimClassNames = new Vector<String>(); rimClassNames.add(RIMClassName(theClass)); addAttributesAndChildren(elementDef, theClass,rimClassNames); } /** * @param theClass EClass for an RMIM class * @return the name of the RIM class it is a clone of */ private String RIMClassName(EClass theClass) {return ModelUtil.getEAnnotationDetail(theClass, "RIM Class");} /** * @param elementDef an ElementDef to be put in the MappedStructure for an RMIM * @param theClass the class it will represent * @param rimClassNames Vector to avoid infinite recursion * Add AttibuteDefs and for EAttributes of the class, and child * ElementDefs for EReferences of the class, recursively down * the RMIM tree until you come to a class in another package (RMIM). */ private void addAttributesAndChildren(ElementDef elementDef, EClass theClass, Vector<String> rimClassNames) { // treeSize++; // trace("Depth " + classNames.size() + " " + theClass.getName() + " " + elementCount); // as we are adding attributes and child elements to this ElementDef, it is expanded elementDef.setExpanded(true); /* for each EAttribute of the class (RIM Structural attribute), make a child AttributeDef; * and for each EReference, make a child ElementDef*/ for (Iterator <EStructuralFeature> it = theClass.getEAllStructuralFeatures().iterator();it.hasNext();) { EStructuralFeature next = it.next(); if (next instanceof EAttribute) { EAttribute att = (EAttribute)next; AttributeDef atDef = MapperFactory.eINSTANCE.createAttributeDef(); atDef.setName(att.getName()); atDef.setMinMultiplicity(MinMult.get(att.getLowerBound())); elementDef.getAttributeDefs().add(atDef); } else if (next instanceof EReference) { EReference ref = (EReference)next; EClass childClass = (EClass)ref.getEType(); EPackage childPackage = childClass.getEPackage(); if (childPackage != null) { String childPackageName = childClass.getEPackage().getName(); ElementDef childElementDef = MapperFactory.eINSTANCE.createElementDef(); // the name of the child element is the association role name childElementDef.setName(ref.getName()); childElementDef.setMinMultiplicity(MinMult.get(ref.getLowerBound())); // {0,1} => {0,1} // default MaxMultiplicity of ElementDef is 1 if (ref.getUpperBound() == -1) childElementDef.setMaxMultiplicity(MaxMult.UNBOUNDED); // the type of the child element is "" unless it is a switch to another mapping set String typeName = ""; if (expandNode(theClass,childClass,rimClassNames)) { Vector<String> newClassNames = new Vector<String>(); for (Iterator<String> is = rimClassNames.iterator();is.hasNext();) newClassNames.add(is.next()); newClassNames.add(RIMClassName(childClass)); // recursion of this method, to fill out structure under the child element addAttributesAndChildren(childElementDef, childClass,newClassNames); } // switch to another mapping set ; stop recursion and note the structure needs to be made else { // the child element in this subtree is not expanded childElementDef.setExpanded(false); // if the type name is the data type name if (childPackageName.equals("datatypes")) typeName = childClass.getName(); // mapped structures have already been made for all the data types else if (!childPackageName.equals("datatypes")) { // the type name for a non-datatype is the child package name followed by the class name typeName = childPackageName + "_" + childClass.getName(); // this will cause the ElementDef structure for the type to be made later allEClasses.put(typeName, childClass); } } childElementDef.setType(typeName); elementDef.getChildElements().add(childElementDef); } else trace("Still no package for class " + childClass.getName()); } } } //----------------------------------------------------------------------------------------------- // Criteria for cutting off ElementDef trees //----------------------------------------------------------------------------------------------- /** * criterion to expand or not expand an ElementDef */ private boolean expandNode(EClass theClass, EClass childClass, Vector<String> rimClassNames) { boolean repeatedRIMClass = false; if (RIMClassName(childClass) != null) // untrue for data type classes repeatedRIMClass = GenUtil.inVector(RIMClassName(childClass), rimClassNames); String packageName = theClass.getEPackage().getName(); String childPackageName = childClass.getEPackage().getName(); // avoid over-large trees by cutting off at depth 2, or if the package changes boolean expandNode = ((childPackageName.equals(packageName)) && (rimClassNames.size() < 2) && !repeatedRIMClass); return expandNode; } /** * print out a list of how many EReferences there are to each class, * in the top sub-packages of a main package * @param topPackage * @return references: for each class, the number of references to it plus one */ private Hashtable<String,Integer> countReferencesToClasses(EPackage topPackage) { Hashtable<String,Integer> references = new Hashtable<String,Integer>(); for (Iterator<EPackage> ip = topPackage.getESubpackages().iterator();ip.hasNext();) { EPackage subPackage = ip.next(); for (Iterator<EClassifier> ic = subPackage.getEClassifiers().iterator();ic.hasNext();) { EClassifier next = ic.next(); saveCount(next.getName(),references); // one reference for existing if (next instanceof EClass) { EClass theClass = (EClass)next; for (Iterator<EReference> ir = theClass.getEReferences().iterator();ir.hasNext();) saveCount(ir.next().getEType().getName(),references); } } } trace("Count of references"); boolean isFalse = false; if (isFalse) for (Enumeration<String> en = references.keys(); en.hasMoreElements();) { String target = en.nextElement(); // for each class, remove the score 1 for existing trace(target + ", " + (references.get(target).intValue()- 1)); } return references; } private void saveCount(String target, Hashtable<String,Integer> references) { Integer count = references.get(target); if (count == null) count = new Integer(1); else count = new Integer(count.intValue() + 1); references.put(target,count); } /** * @param elDef root of an ElementDef tree * @return number of ElementDefs in the tree */ public int getElementSize(ElementDef elDef) { int size = 1; for (Iterator<ElementDef> it = elDef.getChildElements().iterator(); it.hasNext();) size = size + getElementSize(it.next()); return size; } //----------------------------------------------------------------------------------------------- // Interface StructureDefinition //----------------------------------------------------------------------------------------------- /** * find the Element and Attribute structure of some named top element (which may have a named * complex type, or a locally defined anonymous type), stopping at the * next complex type definitions it refers to * @param String name the name of the element * @return null, as this method is not used */ public ElementDef nameStructure(String name) throws MapperException { boolean isTrue = true; if (isTrue) throw new MapperException("Unexpected call for a MIF structure ElementDef of name '" + name + "'"); return null; } /** * find the Element and Attribute structure of some complex type, stopping at the * next complex type definitions it refers to * @param type the name of the complex type * @return the EObject subtree (Element and Attribute EObjects) defined by the type */ public ElementDef typeStructure(String type) throws MapperException { return allStructures.get(type); } /** * * @return an array of the top-level complex types defined in the structure definition - * any of which can be the type of a mapping set */ public String[] topComplexTypes() { ArrayList<String> allTypes = new ArrayList<String>(); allTypes.add(""); // the default choice on the menu, before any choice is made, is "" for (Enumeration<String> en = allStructures.keys();en.hasMoreElements();) allTypes.add(en.nextElement()); String[] res = new String[allTypes.size()]; return allTypes.toArray(res); } /** * @return the set of namespaces defined for the structure */ public NamespaceSet NSSet() { NamespaceSet nss = new NamespaceSet(); try{ // always add the xsi namespace nss.addNamespace(new namespace(XMLUtil.SCHEMAINSTANCEPREFIX,XMLUtil.SCHEMAINSTANCEURI)); // for CDA RMIMs...(NHS or otherwise) EPackage classPackage = topClasses.get(0).getEPackage(); boolean addV3Namespace = ((classPackage.getNsPrefix() != null) && (classPackage.getNsPrefix().equals(RMIMReader.CDAPREFIX))); if (rmimReader.isNHSMIF()) addV3Namespace = true; if (addV3Namespace) { // add the HL7 V3 namespace with no prefix nss.addNamespace(new namespace("",CDAConverter.V3NAMESPACEURI)); // add the Vocabulary namespace nss.addNamespace(new namespace(RMIMReader.VOCABULARYNAMESPACEPREFIX,RMIMReader.VOCABULARYNAMESPACEURI)); } } catch(NamespaceException ex) {} return nss; } //----------------------------------------------------------------------------------------------- // Interface PropertyValueSupplier //----------------------------------------------------------------------------------------------- /** * * @param modelClassName * @param modelPropertyName * @return true if this property value supplier supplies values for the mapper * model class and property */ public boolean suppliesPropertyValues(String modelClassName, String modelPropertyName) { if ((modelClassName.equals("MappedStructure")) && (modelPropertyName.equals("Top Element Type"))) return true; if ((modelClassName.equals("MappedStructure")) && (modelPropertyName.equals("Top Element Name"))) return true; return false; } /** * * @param modelClassName * @param modelPropertyName * @return the values supplied by this supplier for the mapper model class and property */ public String[] propertyValues(String modelClassName, String modelPropertyName) { String[] vals = {}; if ((modelClassName.equals("MappedStructure")) && (modelPropertyName.equals("Top Element Type"))) return topComplexTypes(); if ((modelClassName.equals("MappedStructure")) && (modelPropertyName.equals("Top Element Name"))) return new String[0]; return vals; } //--------------------------------------------------------------------------------------------------- // Trivia //--------------------------------------------------------------------------------------------------- private void trace(String s) {if (tracing) System.out.println(s);} }