package com.openMap1.mapper.health.cda; import java.util.Collections; 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 org.eclipse.emf.ecore.EcoreFactory; import org.w3c.dom.Element; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.converters.CDAConverter; import com.openMap1.mapper.health.v3.ConcreteClass; import com.openMap1.mapper.health.v3.RMIMReader; import com.openMap1.mapper.util.GenUtil; import com.openMap1.mapper.util.ModelUtil; import com.openMap1.mapper.util.XMLUtil; /** * represents the full collection of templates defined in the template usage file * @author robert * */ public class TemplateCollection { private boolean tracing = true; // for trace of activity public boolean tracing() {return tracing;} private Element templateUsageRoot; // root element of the template usage file private RMIMReader rmimReader;// reads the unconstrained CDA RMIM private Vector<TemplateSet> templateSets;// template sets, eg CCD, HITSP C32, etc. private EPackage copyModelPackage; // the overall package containing the constrained Ecore model private EPackage copyRMIMPackage; // the package for constrained RMIM classes private EPackage copyDataTypePackage; // the data types package in the constrained model private EClass constrainedTopClass; // root class of the constrained model private boolean makeDataTypeMappings = true; /** * @return List of top classes of the constrained Ecore model, used in making mapping sets */ public List<EClass> topClasses() { Vector<EClass> topClasses = new Vector<EClass>(); topClasses.add(constrainedTopClass); return topClasses; } /** * If a CDA document states that it complies with HITSP C32 * (i.e has the HITSP C32 template 1 on its ClinicalDocument node), * and HITSP C32 contains a template H, which is a further constraint on CCD template C, * then is the document * (a) allowed to contain nodes which are constrained only by C? * [strictTemplateConstraint = false] * or (b) must every node which is constrained by C also be constrained by H? * [strictTemplateConstraint = true] * See Dev notes 5 November 2009. * */ // private boolean strictTemplateConstraint = false; /** * long name = CDA class name, followed by sorted concatenated template ids * short name = CDA class name, followed by a template name, followed by an index to make it unique */ private Hashtable<String,EClass> longNamesToClasses; private Hashtable<String,String> shortToLongNames; //--------------------------------------------------------------------------------------------- // Constructor and reading template files //--------------------------------------------------------------------------------------------- public TemplateCollection(Element templateUsageRoot, RMIMReader rmimReader) { this.templateUsageRoot = templateUsageRoot; this.rmimReader = rmimReader; templateSets = new Vector<TemplateSet>(); } /** * read the top template files, with file paths as defined in the template usage file */ public void readTemplateFiles() throws MapperException { String[] topChildren = {"templateSet"}; String[] topAtts = {"name","schematronFolderPath"}; XMLUtil.checkChildElements(templateUsageRoot, topChildren); XMLUtil.checkAttributes(templateUsageRoot, topAtts); String schematronFolderPath = templateUsageRoot.getAttribute("schematronFolderPath"); Vector<Element> tempSetEls = XMLUtil.namedChildElements(templateUsageRoot, "templateSet"); for (int i = 0; i < tempSetEls.size(); i++) { Element templateSetEl = tempSetEls.get(i); String fileName = templateSetEl.getAttribute("schFileName"); String folderName = templateSetEl.getAttribute("subFolder"); String templatePath = schematronFolderPath + "/" + folderName + "/" + fileName; TemplateSet templateSet = new TemplateSet(this,templatePath,templateSetEl); addTemplateSet(templateSet); } } /** * add a template set to the collection * @param templateSet the template set to be added */ public void addTemplateSet(TemplateSet templateSet) { templateSets.add(templateSet); } /** * check that all parents of every template exist, and are at a lower level than it. * check that if any template constrains another one, * (a) the constrained template exists * (b) the constrained template is at the same level as the constraining template * (c) the constrained and constraining template can exist inside the same outer template * (d) the constrains relation is not circular * Throw an exception on any violation. */ public void checkTemplateLevels() throws MapperException { // check all templates in all template sets for (Iterator<CDATemplate> it = allTemplates(); it.hasNext();) { CDATemplate temp = it.next(); Vector<CDATemplate> temps = new Vector<CDATemplate>(); checkConstraintsNotCircular(temp,temps); int level = temp.level(); // find all parents; throw an exception if any do not exist for (Iterator<CDATemplate> ip = temp.allParentTemplates().iterator(); ip.hasNext();) { CDATemplate parent = ip.next(); // any parent's level must be no higher than the template's own level if (parent.level() > level) throw new MapperException(temp.descriptiveName() + " has a lower level than its parent " + parent.descriptiveName()); } // find all constrained templates; throw an exception if any do not exist for (Iterator<CDATemplate> ic = temp.constrainedTemplates().iterator(); ic.hasNext();) { CDATemplate con = ic.next(); // any constrained template's level must be equal to the template level if (con.level() != level) throw new MapperException(temp.descriptiveName() + " has level unequal to its constrained template " + con.descriptiveName()); /* every template that this template can be inside must be one of those * which its constrained template can be inside */ Vector<CDATemplate> constrainedInside = con.allParentTemplates(); for (Iterator<CDATemplate> ip = temp.allParentTemplates().iterator(); ip.hasNext();) { CDATemplate parent = ip.next(); if (!parent.oneOf(constrainedInside)) throw new MapperException(temp.descriptiveName() + " can be inside " + parent.descriptiveName() + " but constrains " + con.descriptiveName() + " which cannot."); } } } } /** * check that the 'constrains' relation between templates is not circular * @param temp a template * @param temps the chain of templates that constrain one another to this point * @throws MapperException */ private void checkConstraintsNotCircular(CDATemplate temp, Vector<CDATemplate> temps) throws MapperException { Vector<CDATemplate> newTemps = new Vector<CDATemplate>(); for (Iterator<CDATemplate> it = temps.iterator(); it.hasNext();) { CDATemplate next = it.next(); newTemps.add(next); if (next.fullTemplateId().equals(temp.fullTemplateId())) throw new MapperException("Circular 'constrains' relation between templates at template '" + temp.fullTemplateId() + "'"); } newTemps.add(temp); for (Iterator<CDATemplate> iu = temp.directConstrainedTemplates().iterator(); iu.hasNext();) { CDATemplate deeper = iu.next(); checkConstraintsNotCircular(deeper, newTemps); } } //--------------------------------------------------------------------------------------------- // Accessors and iterators over templates //--------------------------------------------------------------------------------------------- /** * @return a plain iterator over all CDATemplate objects, in no special order */ public Iterator<CDATemplate> allTemplates() { Vector<CDATemplate> templates = new Vector<CDATemplate>(); for (Iterator<TemplateSet> is = templateSets.iterator(); is.hasNext();) { for (Enumeration<CDATemplate> en = is.next().templates().elements();en.hasMoreElements();) templates.add(en.nextElement()); } return templates.iterator(); } /** * @param templateId a template id * @return the template with that id, or null if there is none */ public CDATemplate getTemplate(String templateId) { CDATemplate temp = null; for (Iterator<CDATemplate> it = allTemplates();it.hasNext();) { CDATemplate candidate = it.next(); if (candidate.fullTemplateId().equals(templateId)) temp = candidate; } return temp; } /** * @param name: name of a template set, e.g. 'CCD' * @return the template set; or null if there is none */ public TemplateSet getTemplateSet(String name) { TemplateSet result = null; for (int i = 0; i < templateSets.size();i++) if (templateSets.get(i).getName().equals(name)) result = templateSets.get(i); return result; } /** * @param context a CDAContext (mentioning various templates) * @return all rules applicable in this context - because their template * is on one of the nodes and the further path from the template node to the rule * node is compatible with the context. * As a side-effect, sets the context step number for each rule */ public Iterator<TemplateRule> applicableRules(TemplatedPath context) { Vector<TemplateRule> rules = new Vector<TemplateRule>(); // loop over context steps for (int s = 0; s < context.length(); s++) { // loop over templates on each context step for (Iterator<String> is = context.step(s).templateIds().iterator();is.hasNext();) { CDATemplate template = getTemplate(is.next()); // loop over rules on each context for (Iterator<TemplateRule> ir = template.getRules().iterator(); ir.hasNext();) { TemplateRule rule = ir.next(); if (rule.matchesContext(context, s)) rules.add(rule); } } } return rules.iterator(); } //--------------------------------------------------------------------------------------------- // Resolving templates //--------------------------------------------------------------------------------------------- /** * Resolve all templates. A template is resolved when all the full contexts * in which it can appear have been found. */ public void resolveAllTemplates() throws MapperException { trace("****Resolving templates"); // resolve the root templates of all template sets for (Iterator<TemplateSet> it = this.templateSets.iterator();it.hasNext();) { TemplateSet tSet = it.next(); String rootTemplateId = tSet.rootTemplateId(); CDATemplate rootTemplate = tSet.getTemplateByFullId(rootTemplateId); if (rootTemplate == null) throw new MapperException("No root template found for template set " + tSet.getName()); TemplatedPath rootContext = new TemplatedPath(); ContextStep rootStep = new ContextStep(null,getEntryClass().getName()); rootContext.addStep(rootStep); rootTemplate.resolveRootContext(rootContext); // trace("Resolved root template " + rootTemplate.templateId() + " to context " + rootContext.stringForm()); } /* iterate over all templates and resolve them. No template can be resolved * until all the templates it can appear directly inside have been resolved. * Keep iterating over all templates until no templates are newly resolved in one pass. */ boolean someResolvedThisPass = true; while (someResolvedThisPass) { someResolvedThisPass = false; for (Iterator<CDATemplate> it = allTemplates(); it.hasNext();) { CDATemplate template = it.next(); /* on each pass, choose only templates that have not been resolved but * whose parent templates have all been resolved */ if ((!template.resolved()) && (template.parentsAllResolved())) { someResolvedThisPass = true; // attempt to set the node name for the template, from its defining assertions template.setNodeNames(); /* resolve the template contexts from its parent contexts (even if it * has no node names - to mark it resolved and stop the iteration)*/ template.resolve(); // if some node names were found if (template.nodeNames().size() > 0) { trace(""); trace ("Template " + template.getLocalId() + "(" + template.name() + ") of template set " + template.templateSet().getName() + " has " + template.allContexts().size() + " contexts and node names " + template.allNodeNames()); for (int t = 0; t < template.allContexts().size(); t++) trace(template.allContexts().get(t).stringForm()); } else if (template.nodeNames().size() == 0) trace("No node names found for template " + template.getLocalId() + " of template set " + template.templateSet().getName()); } } } writeUnresolvedTemplates(); } public void writeUnresolvedTemplates() { trace("\nTemplates not resolved:"); for (Iterator<CDATemplate> it = allTemplates(); it.hasNext();) { CDATemplate template = it.next(); if (!template.resolved()) trace(template.getLocalId()); } } //--------------------------------------------------------------------------------------------- // Constraining the Ecore model //--------------------------------------------------------------------------------------------- /** * Make a constrained Ecore model, by applying the resolved * templates to the CDA Ecore model */ public EPackage makeConstrainedCDAECoreModel(String ecoreFilePath) throws MapperException { trace("Constraining CDA Ecore model"); // writeAllTemplates(); longNamesToClasses = new Hashtable<String,EClass>(); shortToLongNames = new Hashtable<String,String>(); // create three packages for the constrained model (instance variables of this class) createCopyPackages(ecoreFilePath); // create an EClass for the top class of the constrained model, and confirm it EClass cdaTopClass = getEntryClass(); constrainedTopClass = findOrMake(cdaTopClass.getName()); shallowCopy(cdaTopClass,constrainedTopClass); confirmInPackage(constrainedTopClass); Vector<EClass> newClasses = extendClassList(new Vector<EClass>(),constrainedTopClass); // initial template context, without any templates. Null association for the first step. TemplatedPath context = extendContext(new TemplatedPath(),null,cdaTopClass.getName()); /* add templates to the top node and the initial context; * there is only one group of contexts that can co-exist, i.e all the templates for that node */ Vector<CDATemplate> topTemplates = templatesForTopContext(context); addTemplatesToContext(context,topTemplates); addTemplateAnnotations(context,constrainedTopClass,topTemplates); // recursive descent of the CDA model extendConstrainedCDAModel(cdaTopClass, newClasses, context); return copyModelPackage; } /** * Create the packages that will be used for the constrained CDA Ecore model */ private void createCopyPackages(String ecoreFilePath) throws MapperException { // make the overall model package copyModelPackage = EcoreFactory.eINSTANCE.createEPackage(); copyModelPackage.setName("constrainedCDAModel"); copyModelPackage.setNsPrefix("cdam"); copyModelPackage.setNsURI("CDAModel"); // ensure this class model will be viewed as a templated RMIM in the class model view ModelUtil.addMIFAnnotation(copyModelPackage, "RMIM", "true"); ModelUtil.addMIFAnnotation(copyModelPackage, "templated", "true"); // add a package for the CDA RMIM copyRMIMPackage = EcoreFactory.eINSTANCE.createEPackage(); copyRMIMPackage.setName(CDAConverter.constrainedRMIMPackageName); copyRMIMPackage.setNsPrefix(RMIMReader.CDAPREFIX); copyRMIMPackage.setNsURI(CDAConverter.V3NAMESPACEURI); copyModelPackage.getESubpackages().add(copyRMIMPackage); // re-use the data types package, adding it as a sub-package copyDataTypePackage = rmimReader.v3DataTypeHandler().readDataTypeSchema(ecoreFilePath,makeDataTypeMappings); copyModelPackage.getESubpackages().add(copyDataTypePackage); } /** * * @param topPackage * @return true if this model is a templated RMIM */ public static boolean isConstrainedRMIM(EPackage topPackage) { String isRMIM = ModelUtil.getEAnnotationDetail(topPackage, "RMIM"); if (isRMIM == null) return false; if (!isRMIM.equals("true")) return false; String isTemplated = ModelUtil.getEAnnotationDetail(topPackage, "templated"); if (isTemplated == null) return false; if (!isTemplated.equals("true")) return false; return true; } /** * recursive descent of the CDA Ecore model, to fill out the constrained model, * applying constraints and splitting classes as defined by templates. * * @param cdaClass a class in the unconstrained CDA RMIM model * @param copyClasses Vector of classes for the steps of the current context, in the constrained model. * The last of these corresponds to the class cdaClass; * it has been confirmed (put in the RMIM package) and * shallow copied from cdaClass (i.e its immediate child classes exist), * but its child classes have not yet been put in the package. * @param templatedPath the template context. * * There are always some constraints in the subtree below the final node of the * context; this method is not called otherwise */ private void extendConstrainedCDAModel(EClass cdaClass, Vector<EClass> copyClasses, TemplatedPath templatedPath) throws MapperException { // get the class at the end of the chain, which is being worked on EClass copyClass = copyClasses.get(copyClasses.size() - 1); String tPath = templatedPath.stringForm(); String cName = copyClass.getName(); trace("Extend at trail " + tPath + " and class " + cName); /* 1. Check if the cardinality of the class node is constrained * by any template rule. If so, apply the constraint, checking back * up the tree as far as the context node of the constraining rule * (lowerBound or upperBound of all EReferences must be constrained) */ int maxIs1Step = cardinalityConstrainedFromStep(templatedPath, true); if (maxIs1Step > -1) constrainCardinalityFromStep(templatedPath, copyClasses,maxIs1Step,true); int minIs1Step = cardinalityConstrainedFromStep(templatedPath, false); if (minIs1Step > -1) constrainCardinalityFromStep(templatedPath, copyClasses,maxIs1Step,false); /* 2. For each attribute of the eClass, iterate over all applicable templates * (all rules, all constraining assertions) to see if any constrain the attribute * value. */ // fixAttributes(copyClass, context); // to record which associations need to be removed, because templated copies of them have been made Hashtable<String,String> associationsToRemove = new Hashtable<String,String>(); /* main loop over child classes of the copied class. * Copy the reference list first,in case you add to it (?) */ Vector<EReference> refs = new Vector<EReference>(); for (Iterator<EReference> iz = copyClass.getEAllReferences().iterator();iz.hasNext();) {refs.add(iz.next());} for (Iterator<EReference> ir = refs.iterator();ir.hasNext();) { EReference copyRef = ir.next(); String copyRefName = copyRef.getName(); // get the corresponding association in the unconstrained model String modelRefName = ModelUtil.getEAnnotationDetail(copyRef, "CDA_Name"); // if (modelRefName != null) trace("Renamed ref '" + copyRefName + "' at " + nameTrail(copyClasses)); if (modelRefName == null) modelRefName = copyRefName; // FIXME: get round this problem properly if (modelRefName.endsWith("_T")) modelRefName = modelRefName.substring(0,modelRefName.length()-2); /* 3. For each EReference of the class, check if the target node brings in * any more templates. */ EClass modelChildClass = getChildClass(cdaClass,modelRefName); EClass copyChildClass = getChildClass(copyClass,copyRefName); TemplatedPath childContext = extendContext(templatedPath,modelRefName,modelChildClass.getName()); Vector<Vector<CDATemplate>> templateSets = templateSetsForContext(childContext); // if (!isDataTypeClass(modelChildClass)) trace("Found " + templates.size() + " template groups for child class " + modelChildClass.getName()); if (isDataTypeClass(modelChildClass)) { // trace("Data type class " + modelChildClass.getName()); /* Nothing to do now. copyClass has been extended by shallowCopy, which * made copyChildClass to be the appropriate data type class. * This {} is put in to make the subsequent 'elses' not fire for * a data type class. */ } /* If the target node is an ActRelationship and has no templates itself, * go to the descendant Act clones (try all possible choices � encounter, supply, etc�.) * to see if they bring in any more templates. If so, split the ActRelationship */ else if ((CDAConverter.isActRelationship(modelChildClass)) && (templateSets.size() == 0)) { // the copy ActRelationship needs its Act child classes for both cases below shallowCopy(modelChildClass,copyChildClass); /* If the ActRelationship clone has no templates on its Act clone * child classes, carry on the recursion */ int branches = actChildrenWithTemplates(modelChildClass, childContext); // trace("branches: " + branches); if (branches == 0) { boolean constraints = doNoTemplatesCase(childContext,modelChildClass, copyClass, copyRef, copyChildClass,copyClasses); if (constraints) associationsToRemove.put(copyRef.getName(),"1"); } else if (branches > 0) { /* note that you are eventually going to remove this association to the ActRelationship class, * because there are templated versions of it. */ associationsToRemove.put(copyRef.getName(), "1"); int branch = 0; // find all Act classes beneath the ActRelationship, and their contexts for (Iterator<EReference> ix = modelChildClass.getEAllReferences().iterator();ix.hasNext();) { EReference nextRef = ix.next(); String nextRefName = nextRef.getName(); EClass modelActClass = getChildClass(modelChildClass,nextRefName); if (!isDataTypeClass(modelActClass)) { TemplatedPath actContext = extendContext(childContext,nextRefName,modelActClass.getName()); templateSets = templateSetsForContext(actContext); // trace("Act class " + modelActClass.getName() + " has " + templates.size() + " exclusive template groups"); // do nothing for this Act clone class if it has no templates (others do) if (templateSets.size() > 0) { for (Iterator<Vector<CDATemplate>> ics = templateSets.iterator();ics.hasNext();) { Vector<CDATemplate> coexistent = ics.next(); EClass templatedActClass = rearrangeActRelationship(coexistent,cdaClass,copyClasses,copyRef, modelChildClass,copyChildClass,nextRef, modelActClass,actContext,branch,branches); if (templatedActClass != null) addTemplateAnnotations(actContext,templatedActClass,coexistent); } // end of loop over mutually exclusive sets of templates branch++; // count the Act clones with templates } // end of 'if (templates.size() > 0)' section } // end of 'if (!isDataTypeClass(modelActClass))' section } // end of loop over all Act classes beneath the ActRelationship } // end of 'if (branches > 0)' section } // end of 'if (isActRelationship(modelChildClass))' section /* 4. If no templates are found on the target node, check if there are any templates or * constraints in the subtree. If not, switch to plain copy of the tree. * If there are still deeper templates: � Make a shallow copy of the class and give it a new unique name � Clone the context and add a step, adding no new templates � Recursively descend */ else if (templateSets.size() == 0) { boolean constraints = doNoTemplatesCase(childContext,modelChildClass,copyClass, copyRef, copyChildClass,copyClasses); if (constraints) associationsToRemove.put(copyRef.getName(),"1"); } /* 6. If any templates are found, split the templates into mutually * exclusive sets. Iterate over the sets. * For each mutually exclusive set: � Make a shallow copy of the class with a new unique name � Clone the context, and add the new templates on the appropriate node � Recursively descend */ else if (templateSets.size() > 0) { trace("Template sets: " + templateSets.size() + " for path " + childContext.stringForm()); // loop over groups of mutually consistent templates for (Iterator<Vector<CDATemplate>> ics = templateSets.iterator();ics.hasNext();) { Vector<CDATemplate> coexistent = ics.next(); String templateName = mostSpecificTemplate(coexistent).name(); // if the template class exists, find it and attach it; otherwise make it boolean templateClassExistedAlready = templateClassExists(cdaClass,copyRef,coexistent); EClass cloneChildClass = makeOrFindTemplateClass(cdaClass,copyClass,copyRef,templateName,coexistent); // if the template class was newly made, continue the recursion if (!templateClassExistedAlready) { addTemplateAnnotations(templatedPath,cloneChildClass,coexistent); Vector<EClass> cloneClasses = extendClassList(copyClasses,cloneChildClass); TemplatedPath cloneContext = childContext.clone(); addTemplatesToContext(cloneContext,coexistent); extendConstrainedCDAModel(modelChildClass, cloneClasses,cloneContext); } } /* note that you are going to remove this association to the class, * because there are templated versions of it. */ associationsToRemove.put(copyRef.getName(), "1"); } // end of 'if (templates.size() > 0)' section } // end of main loop over EReferences // remove the associations that have been templated, for some classes if (removeRenamedRelations(copyClass)) removeAssociations(copyClass,associationsToRemove); } /** * @param coexistent a Vector of templates that can coexist on the same class * @return the name of the most specific template * - one which is not constrained by any of the others. * It does not matter if there is more than one, and this method makes an arbitrary choice. */ private CDATemplate mostSpecificTemplate(Vector<CDATemplate> coexistent) throws MapperException { CDATemplate mostSpecific = null; String allNames = ""; // for message in case of exception for (Iterator<CDATemplate> it = coexistent.iterator();it.hasNext();) { CDATemplate temp = it.next(); allNames = allNames + temp.descriptiveName() + "; "; // check if this template is constrained by any others in the set boolean constrained = false; for (Iterator<CDATemplate> ic = coexistent.iterator();ic.hasNext();) { CDATemplate con = ic.next(); if (con.constrains(temp)) constrained = true; } // remember any unconstrained template if (!constrained) mostSpecific = temp; } if (mostSpecific == null) throw new MapperException("Cannot find an unconstrained template in set " + allNames); return mostSpecific; } /** * @param theClass a class made by the template specialisation * @return true if, for any association renamed, * the original should be removed from the model. * This is only so for classes that are expected to have just one child class */ private boolean removeRenamedRelations(EClass theClass) { String[] singleChildRIMClasses = {"ActRelationship","Participation"}; boolean remove = false; String RIMClass = ModelUtil.getEAnnotationDetail(theClass, "RIM Class"); if ((RIMClass != null) && (GenUtil.inArray(RIMClass, singleChildRIMClasses))) remove = true; return remove; } /** * Handle the case where no new templates are found on the latest * extension of the context path * @param context the new context - no templates on its last step * @param modelClass the new child class in the unconstrained CDA model * @param copyParentClass the parent class in the constrained copy model * @param copyRef the EReference from the constrained parent to the constrained child * @param copyClass the constrained child class * @param copyClasses Vector of copied classes, matching newContext except for the last element * * Check if there are any templates or constraints in the subtree. * If not, switch to a plain copy of the tree. * If there are still deeper templates: � Make a shallow copy of the class and give it a new unique name � Clone the context and add a step, adding no new templates � Recursively descend */ private boolean doNoTemplatesCase(TemplatedPath context,EClass modelClass, EClass copyParentClass, EReference copyRef, EClass copyClass, Vector<EClass> copyClasses) throws MapperException { // trace("No-template case for class " + modelClass.getName() + " at context " + context.stringForm()); boolean constraints = constraintsInSubtree(context); // no changes to come in subtree; use plain copy (if the class is not already in the package) if (!constraints) { if (copyRMIMPackage.getEClassifier(copyClass.getName()) == null) { // no need for a name change, as descendant subtree is unconstrained confirmInPackage(copyClass); plainCopy(modelClass, copyRef,copyClass); } // if the class is already in the package,do nothing } /* changes are expected in the subtree from rules in existing templates, or from new * templates; make a copy with a new name, and extend recursively */ else { EClass copyCloneClass = copyWithNewUniqueName(copyParentClass,copyRef,"T"); // look at parent to change its EReference name; no template name // put the class with a new name in the package confirmInPackage(copyCloneClass); // put in package, now its name is fixed String CDAName = ModelUtil.getEAnnotationDetail(copyCloneClass, "CDA_Name"); shallowCopy(modelClass, copyCloneClass); // destroys previous annotations ModelUtil.addMIFAnnotation(copyCloneClass, "CDA_Name", CDAName); Vector<EClass> newCopyClasses = extendClassList(copyClasses,copyCloneClass); extendConstrainedCDAModel(modelClass, newCopyClasses,context); } // trace("Finished no template case for class " + modelClass.getName() + "; result " + constraints); return constraints; } /** * make the rearranged ActRelationship clone class, with just one Act clone class beneath it, * for one branch of a split caused by templates on one or more Act children of * the one ActRelationship class in the unconstrained model * * @param coexistent a set of templates that can coexist on one Act clone node * @param copyClasses Vector of classes down the current branch of the model, down to the * parent of the ActRelationship class * @param refToActRel association from the ActRelationship's parent to the ActRelationship * @param modelActRelClass ActRelationship class in the unconstrained model * @param copyActRelClass ActRelationship class in the constrained model, which is to be split * @param refToAct association from the ActRelationship to the current child Act clone * @param modelActClass current Act clone class, which has templates * @param actContext context path as far as the Act clone class * @return the templated Act class if it has to be made; null if it exists already * @throws MapperException */ private EClass rearrangeActRelationship(Vector<CDATemplate> coexistent, EClass modelParentClass, Vector<EClass> copyClasses, EReference refToActRel, EClass modelActRelClass, EClass copyActRelClass,EReference refToAct, EClass modelActClass,TemplatedPath actContext,int branch, int branches) throws MapperException { // trace("ActRelationship class " + modelActRelClass.getName() + " with Act child " + modelActClass.getName()); // parent class of the ActRelationship, in the constrained copy EClass copyParentClass = copyClasses.get(copyClasses.size() - 1); // current Act class in the copy of the model EClass copyActClass = getChildClass(copyActRelClass,refToAct.getName()); // use the the most specific (constraining) template name in all new names, and note its local id String templateName = mostSpecificTemplate(coexistent).name(); @SuppressWarnings("unused") String localId = mostSpecificTemplate(coexistent).getLocalId(); /// only used for tracing /* need to differentiate different Act classes which the template may match inside the * ActRelationship class, when looking for existing template classes with a given name */ if (branches > 1) templateName = templateName + "_" + modelActClass.getName(); boolean templateActRelClassExisted = templateClassExists(modelParentClass,refToActRel,coexistent); EClass templateActRelClass = makeOrFindTemplateClass(modelParentClass,copyParentClass,refToActRel,templateName,coexistent); EClass templateActClass = null; // return non-null only if it has to be made // if the ActRelationship templated class is new, make the tree beneath it if (!templateActRelClassExisted) { Vector<EClass> actRelClasses = extendClassList(copyClasses,templateActRelClass); /* remove all associations of the renamed ActRelationship class, * and add one association to the copy Act clone */ ModelUtil.removeEReferences(templateActRelClass); // make an EReference, to copy in makeOrFindTemplateClass EReference newRefToAct = EcoreFactory.eINSTANCE.createEReference(); newRefToAct.setName(refToAct.getName()); // old name; will be ignored newRefToAct.setLowerBound(1); newRefToAct.setUpperBound(1); newRefToAct.setContainment(true); newRefToAct.setEType(copyActClass); templateActClass = makeOrFindTemplateClass (modelActRelClass,templateActRelClass,newRefToAct,templateName,coexistent); // trace("Classes for template " + localId + ": " + templateActRelClass.getName() + " " + templateActClass.getName()); // add the templates to the context, and carry on the recursion TemplatedPath actContextClone = actContext.clone(); addTemplatesToContext(actContextClone,coexistent); Vector<EClass> actClasses = extendClassList(actRelClasses,templateActClass); extendConstrainedCDAModel(modelActClass, actClasses,actContextClone); } // trace("Finished ActRelationship class " + modelActRelClass.getName()); return templateActClass; } private void addTemplateAnnotations(TemplatedPath context, EClass templatedClass,Vector<CDATemplate> coexistent) { for (int i = 0; i < coexistent.size(); i++) { CDATemplate template = coexistent.get(i); String suffix = ""; if (i > 0) suffix = "_" + i; String templateKey = "template" + suffix; // add one annotation saying the template is on the EClass ModelUtil.addMIFAnnotation(templatedClass, templateKey, template.fullTemplateId()); // add annotations for each constraint the template puts on descendant nodes template.addConstraintAnnotations(context, templatedClass,suffix); } } //--------------------------------------------------------------------------------------------- // Small methods in support of constraining the CDA Ecore model //--------------------------------------------------------------------------------------------- /** * @param context * @param templates * record the ids of all the templates on the inner step of a context */ private void addTemplatesToContext(TemplatedPath context,Vector<CDATemplate> templates) { int lastStep = context.length() -1; for (int t = 0; t < templates.size();t++) context.step(lastStep).addTemplateId(templates.get(t).fullTemplateId()); } /** * confirm that a class will now not be renamed or split, * by adding it to the RMIM package of the constrained model; * throw an Exception if it is already in the package * @param theClass */ private void confirmInPackage(EClass theClass) throws MapperException { if (copyDataTypePackage.getEClassifier(theClass.getName()) != null) throw new MapperException("Class " + theClass.getName() + " is already in the copy data types package."); if (copyRMIMPackage.getEClassifier(theClass.getName()) != null) throw new MapperException("Class " + theClass.getName() + " is already in the copy RMIM package."); copyRMIMPackage.getEClassifiers().add(theClass); //trace("Added class " + theClass.getName() + " to copy RMIM package"); } /** * @param classes classes on the steps of the current context * @param newClass class to go on the next step * @return extended list of classes */ private Vector<EClass> extendClassList(Vector<EClass> classes, EClass newClass) { Vector<EClass> newClasses = new Vector<EClass>(); for (int i = 0; i < classes.size(); i++) newClasses.add(classes.get(i)); newClasses.add(newClass); return newClasses; } /** * remove named associations from a class * @param theClass * @param associationsToRemove */ private void removeAssociations(EClass theClass, Hashtable<String,String> associationsToRemove) { for (Enumeration<String> en = associationsToRemove.keys();en.hasMoreElements();) { EStructuralFeature ref = theClass.getEStructuralFeature(en.nextElement()); if (ref != null) { theClass.getEStructuralFeatures().remove(ref); trace("Removing association " + ref.getName() + " from class " + theClass.getName()); } } } @SuppressWarnings("unused") private String nameTrail(Vector<EClass> classes) { String trail = ""; for (Iterator<EClass> it = classes.iterator();it.hasNext();) trail = trail + "/" + it.next().getName(); return trail; } private TemplatedPath extendContext(TemplatedPath context, String assocName, String className) { TemplatedPath newContext = context.clone(); newContext.addStep(new ContextStep(assocName,className)); return newContext; } /** * @param actRelClass and ActRelationship clone * @param context the context at that class * @return the number of Act child classes which carry any templates */ private int actChildrenWithTemplates(EClass actRelClass, TemplatedPath context) throws MapperException { int branches = 0; // find all Act classes beneath the ActRelationship, and their contexts for (Iterator<EReference> ix = actRelClass.getEAllReferences().iterator();ix.hasNext();) { EReference nextRef = ix.next(); String nextRefName = nextRef.getName(); EClass modelActClass = getChildClass(actRelClass,nextRefName); TemplatedPath nextContext = extendContext(context,nextRefName,modelActClass.getName()); Vector<Vector<CDATemplate>> templateSets = templateSetsForContext(nextContext); if (templateSets.size() > 0) branches++; } return branches; } /** * @param context a CDA context * @return true if there are expected to be template constraints * in the subtree below the final node of the context - * either because some templates in the context have rules and assertions * that reach into the subtree, or because there are other * templates in the subtree */ private boolean constraintsInSubtree(TemplatedPath context) throws MapperException { return ((someRulesAffectSubtree(context)) || (moreTemplatesInSubtree(context))); } /** * * @param parentModelClass * @param ref * @param templates * @return true if the template class exists already, so there is no need to extend * it downwards * @throws MapperException */ private boolean templateClassExists(EClass parentModelClass, EReference ref, Vector<CDATemplate> templates) throws MapperException { String refName = ModelUtil.getEAnnotationDetail(ref, "CDA_Name"); if (refName == null) refName = ref.getName(); EClass noTemplateModelClass = getChildClass(parentModelClass,refName); String key = longName(noTemplateModelClass,templates); return (longNamesToClasses.get(key) != null); } /** * Either find a template class already made in the constrained RMIM package, and attach it to its parent; * Or make it, shallow copy it, put it in the package and attach it to its parent. * * @param parentModelClass the class in the unconstrained model whose copy is to be * parent of the template class * @param parentCopyClass the class in the constrained model which is to be * parent of the template class * @param ref the association that the association to the templated class is to be made from * @param templateName the template name * * @return the template class * @throws MapperException */ private EClass makeOrFindTemplateClass(EClass parentModelClass, EClass parentCopyClass, EReference ref, String templateName,Vector<CDATemplate> templates) throws MapperException { EClass templatedClass = null; boolean existsAlready = templateClassExists(parentModelClass,ref, templates); String refName = ModelUtil.getEAnnotationDetail(ref, "CDA_Name"); if (refName == null) refName = ref.getName(); EClass noTemplateModelClass = getChildClass(parentModelClass,refName); String longName = longName(noTemplateModelClass,templates); if (existsAlready) { templatedClass = longNamesToClasses.get(longName); } else { String templateClassName = makeNewTemplatedName(noTemplateModelClass.getName(), templateName); templatedClass = EcoreFactory.eINSTANCE.createEClass(); templatedClass.setName(templateClassName); trace("making new class '" + templateClassName + "' from '" + noTemplateModelClass.getName() + "'"); // ModelUtil.traceRefNames(noTemplateModelClass); shallowCopy(noTemplateModelClass,templatedClass); // ModelUtil.traceRefNames(templatedClass); ModelUtil.addMIFAnnotation(templatedClass, "CDA_Name", noTemplateModelClass.getName()); confirmInPackage(templatedClass); longNamesToClasses.put(longName, templatedClass); shortToLongNames.put(templateClassName, longName); } String templateRefName = makeRefName(templatedClass.getName()); if (parentCopyClass.getEStructuralFeature(templateRefName)== null) { EReference newRef = EcoreFactory.eINSTANCE.createEReference(); newRef.setName(templateRefName); newRef.setEType(templatedClass); newRef.setLowerBound(ref.getLowerBound()); newRef.setUpperBound(ref.getUpperBound()); newRef.setContainment(ref.isContainment()); ModelUtil.addMIFAnnotation(newRef, "CDA_Name", refName); parentCopyClass.getEStructuralFeatures().add(newRef); } return templatedClass; } /** * make a new name for a constrained class, which has not been used before * @param oldName name of a class in the un-templated model * @param templateName the template name which is to be included in the new name * @return a unique new name */ private String makeNewTemplatedName(String oldName, String templateName) { String stem = oldName + "_" + GenUtil.gaplessForm(templateName); String name = stem; int index = 1; while (shortToLongNames.get(name) != null) { name = stem + "_" + index; index++; } return name; } /** * @param className * @return the className converted to a reference name, by making * its first letter lower case */ private String makeRefName(String className) { String initial = className.substring(0,1); String remainder = className.substring(1); return (initial.toLowerCase() + remainder); } /** * @param temps * @return a unique long name, made from a class name and a set of templates, * in a way that does not depend on their order */ private String longName(EClass modelClass, Vector<CDATemplate> temps) { String key = modelClass.getName(); Vector<String> keys = new Vector<String>(); for (Iterator<CDATemplate> it = temps.iterator();it.hasNext();) keys.add(it.next().fullTemplateId()); Collections.sort(keys); for (Iterator<String> iu = keys.iterator(); iu.hasNext();) {key = key + "_" + iu.next();} return key; } /** * * @param parentClass * @param ref * @param templateName * Create a new ERreference on the parent class and a new target class for the EReference - * copied from the supplied EReference and its target class. * Then give a new unique name to the new EReference and new target class * @return the new target class */ private EClass copyWithNewUniqueName(EClass parentClass, EReference ref, String templateName) throws MapperException { EReference newRef = EcoreFactory.eINSTANCE.createEReference(); newRef.setName(ref.getName()); newRef.setLowerBound(ref.getLowerBound()); newRef.setUpperBound(ref.getUpperBound()); newRef.setContainment(ref.isContainment()); parentClass.getEStructuralFeatures().add(newRef); EClassifier oldInner = ref.getEType(); if (oldInner == null) throw new MapperException("Association '" + ref.getName() + "' from class '" + parentClass.getName() + "' has no target class."); EClass newInner = EcoreFactory.eINSTANCE.createEClass(); newInner.setName(oldInner.getName()); newRef.setEType(newInner); return giveNewUniqueName(parentClass, newRef,templateName); } /** * @param parentClass an EClass in the constrained ECore model, which has already been put in an EPackage * @param ref a containment association, whose name is as in the unconstrained model, and which has * a class at the end of it, named as in the unconstrained model. * @param templateName the name of a template which gave rise to this association and class * Give a new name to the association and the inner class, incorporating the template name * if that is not empty. * Ensure that the association name is unique within the parent class, and the the class name * is unique within the package of the parent class. * Annotate both the association and the inner class to say what their names were in * the unconstrained model. * Add the inner class to the EPackage of the parent class * @return the inner class * */ private EClass giveNewUniqueName(EClass parentClass, EReference ref, String templateName) throws MapperException { String gaplessName = GenUtil.gaplessForm(templateName); // non-clashing reference name String oldRefName = ref.getName(); String newRefRoot = oldRefName + "_" + gaplessName; String newRefName = newRefRoot; int refIndex = 0; while (refNameClash(parentClass,newRefName)) { refIndex++; newRefName = newRefRoot + "_" + refIndex; } ref.setName(newRefName); ModelUtil.addMIFAnnotation(ref, "CDA_Name", oldRefName); // non-clashing class name EClass innerClass = (EClass)ref.getEType(); String oldClassName = innerClass.getName(); EPackage thePackage = parentClass.getEPackage(); if (thePackage == null) throw new MapperException("Class " + parentClass.getName() + " is not in a package when renaming association " + newRefName); String newClassRoot = oldClassName + "_" + gaplessName; String newClassName = newClassRoot; int classIndex = 0; while (classNameClash(thePackage,newClassName)) { classIndex++; newClassName = newClassRoot + "_" + classIndex; } innerClass.setName(newClassName); ModelUtil.addMIFAnnotation(innerClass, "CDA_Name", oldClassName); return innerClass; } /** * @param parentClass * @param newRefName * @return true if the proposed EReference name clashes with one already on the EClass */ private boolean refNameClash(EClass parentClass,String newRefName) { boolean clash = false; for (Iterator<EReference> ir = parentClass.getEAllReferences().iterator(); ir.hasNext();) if (ir.next().getName().equals(newRefName)) clash = true; return clash; } /** * @param thePackage * @param newClassName * @return true if the proposed new class name clashes with one already in the package */ private boolean classNameClash(EPackage thePackage,String newClassName) { return (thePackage.getEClassifier(newClassName) != null); } /** * * @param parent a parent class * @param refName the name of a containment association * @return the child EClass reached through the association; must not be null */ private EClass getChildClass(EClass parent,String refName) throws MapperException { EClass child = null; String refNames = ""; for (Iterator<EReference> ir = parent.getEAllReferences().iterator();ir.hasNext();) { EReference ref = ir.next(); refNames = refNames + ref.getName() + " "; if (ref.getName().equals(refName)) child = (EClass)ref.getEType(); } if (child == null) throw new MapperException("Class '" + parent.getName() + "' has no child class via association '" + refName + "'; but has associations " + refNames); return child; } /** * @param context the current context path * @return true if some of the rules in some of the templates in this * context imply constraints on nodes in the subtree below the current * final node of the context */ private boolean someRulesAffectSubtree(TemplatedPath context) { boolean someAffect = false; for (Iterator<TemplateRule> ir = applicableRules(context);ir.hasNext();) { TemplateRule rule = ir.next(); if (rule.constrainsSubtreeBelow(context, rule.getContextStep())) someAffect = true; } return someAffect; } //------------------------------------------------------------------------------------------- // Applying constraints to the Ecore model (will be changed) //------------------------------------------------------------------------------------------- /** * @param context a context path * @param newClasses Vector of EClasses for each step of the path * @param startStep context step of a rule constraining a cardinality * @param isMaximum true if it is a max cardinality; false if a min cardinality * Set the upper or lower bounds of all the named EReferences along the path to 1, * so as to guarantee that the min or max cardinality of the whole constrained path is 1 */ private void constrainCardinalityFromStep(TemplatedPath context, Vector<EClass> newClasses, int startStep, boolean isMaximum) { // iterate over steps from the rule step to the penultimate step of the context for (int s = startStep; s < context.length()-1; s++) { // from the EClass on the step, get the association whose name is the name of the next step for (Iterator<EReference> ir = newClasses.get(s).getEAllReferences().iterator();ir.hasNext();) { EReference ref = ir.next(); if (ref.getName().equals(context.step(s+1).associationName())) { // set the lower or upper bound of the association to 1. if (isMaximum) ref.setUpperBound(1); else ref.setLowerBound(1); } } } } //------------------------------------------------------------------------------------------------ // Copying parts of the Ecore class model //------------------------------------------------------------------------------------------------ /** * @param model the model EClass which a copy is being made of * @param copyParentRef the reference to the copy in the copy model * @param copy an empty EClass which is to be the copy * Make 'copy' a copy of 'model' - having all its EAttributes with the same values, * and all its EReferences having target classes made by recursive descent, only if * they do not exist in the copy package already. * All References to data type classes are copied as EReferences to the appropriate class * in the copy data type package. Copy all annotations. * Inherited EAttributes and EReferences get put onto the copy directly, * rather than being inherited - so this copy does not preserve the inheritance hierarchy */ private void plainCopy(EClass model, EReference copyParentRef,EClass copy) throws MapperException { /* for data type classes in the unconstrained model, set the copy * to be the class with the same name in the copy data types package. */ if (isDataTypeClass(model)) { copy = getDataTypeClass(model.getName(),copyDataTypePackage); copyParentRef.setEType(copy); return; } // trace("Plain copy of class " + model.getName()); /* make the new copy class a shallow copy of the model class, with empty * target classes for the containment EReferences, no yet in the package. */ shallowCopy(model, copy); /* make up the target classes by recursive descent, unless they can be * found already in the copy package. */ for (Iterator<EReference> ir = copy.getEAllReferences().iterator();ir.hasNext();) { EReference copyRef = ir.next(); if (copyRef.isContainment()) { // find the EReference of the same name in the model for (Iterator<EReference> is = model.getEAllReferences().iterator();is.hasNext();) { EReference modelRef = is.next(); if (modelRef.getName().equals(copyRef.getName())) { EClass modelTarget = (EClass)modelRef.getEType(); String targetClassName = modelTarget.getName(); // if the target class already exists in the package, use that version EClassifier properTarget = copyRMIMPackage.getEClassifier(targetClassName); if (properTarget != null) copyRef.setEType(properTarget); /* if the target class has just been made by * shallowCopy and so does not exist in the package, * fill it out recursively and put it in the package */ else { EClass target = (EClass)copyRef.getEType(); if (target.getEPackage() == null) confirmInPackage(target); plainCopy(modelTarget,copyRef,target); } } } } } } /** * @param model the model EClass which a copy is being made of * @param copy an empty EClass * Make 'copy' a shallow copy of 'model' - having all its EAttributes with the same values, * and all its EReferences to RMIM classes with empty EClasses as targets. * All References to data type classes are copied as EReferences to the appropriate class * in the copy data type package. Copy all annotations. * Inherited EAttributes and EReferences get put onto the copy directly, * rather than being inherited - so this copy does not preserve the inheritance hierarchy */ private void shallowCopy(EClass model, EClass copy) throws MapperException { /* do NOT set the name of the copy class from the name of the model class; * sometimes it has been changed. */ ModelUtil.copyMifAnnotations(model, copy); // in case the copy has been shallow copied before copy.getEStructuralFeatures().clear(); for (Iterator<EAttribute> ia = model.getEAllAttributes().iterator();ia.hasNext();) copy.getEStructuralFeatures().add(copyAttribute(ia.next())); for (Iterator<EReference> ir = model.getEAllReferences().iterator(); ir.hasNext();) { EReference ref = ir.next(); // copy the EReference, make and name the target class (do not put it in the package) EReference copyRef = copyReference(ref); copy.getEStructuralFeatures().add(copyRef); } } /** * @param modelRef * @return a copy of the EReference, with as its target EClass: * (a) the named class in the copy RMIM package, if it exists there already * (b) otherwise, a new empty class not in the RMIM package */ private EReference copyReference(EReference modelRef) throws MapperException { EReference copy = EcoreFactory.eINSTANCE.createEReference(); copy.setName(modelRef.getName()); copy.setLowerBound(modelRef.getLowerBound()); copy.setUpperBound(modelRef.getUpperBound()); copy.setContainment(modelRef.isContainment()); EClass modelInner = (EClass)modelRef.getEType(); if (modelInner == null) throw new MapperException("Found no target class for association '" + modelRef.getName() + "'"); EClass target = null; /* for EReferences to data type classes, make the reference * point to the appropriate data type class in the new data type package*/ String classType = " data type "; if (isDataTypeClass(modelInner)) target = getDataTypeClass(modelInner.getName(),copyDataTypePackage); else { classType = " RMIM "; target = findOrMake(modelInner.getName()); // set only the name of the target class } if (target == null) throw new MapperException("Failed to make copy of" + classType + "class '" + modelInner.getName() + "' reached by association '" + modelRef.getName() + "'" ); copy.setEType(target); ModelUtil.copyMifAnnotations(modelRef, copy); return copy; } /** * @param modelAtt * @return a copy of the EAttribute */ private EAttribute copyAttribute(EAttribute modelAtt) { EAttribute copy = EcoreFactory.eINSTANCE.createEAttribute(); copy.setName(modelAtt.getName()); copy.setLowerBound(modelAtt.getLowerBound()); copy.setEType(modelAtt.getEType()); copy.setDefaultValue(modelAtt.getDefaultValue()); ModelUtil.copyMifAnnotations(modelAtt, copy); return copy; } /** * @param className a class name * @return a class with that name - found in the copy RMIM package, * or newly made if not found */ private EClass findOrMake(String className) { EClass newClass = (EClass)copyRMIMPackage.getEClassifier(className); if (newClass != null) return newClass; newClass = EcoreFactory.eINSTANCE.createEClass(); newClass.setName(className); return newClass; } /** * * @param className * @param datatypePackage * @return the named class in the data type package */ private EClass getDataTypeClass(String className,EPackage datatypePackage) throws MapperException { EClass theClass = null; for (Iterator<EClassifier> it = datatypePackage.getEClassifiers().iterator();it.hasNext();) { EClassifier next = it.next(); if ((next instanceof EClass) && (next.getName().equals(className))) theClass = (EClass)next; } if (theClass == null) throw new MapperException("Cannot find data type class '" + className + "'"); return theClass; } /** * * @param modelClass a class in the unconstrained CDA model * @return true if it is a data types class */ private boolean isDataTypeClass(EClass modelClass) { return (modelClass.getEPackage().getName().equals(RMIMReader.DATATYPE_PACKAGE_NAME)); } /** * @return the top ClinicalDocument class of the CDA RMIM */ public EClass getEntryClass() { return ((ConcreteClass)(rmimReader.topRMIM()).getEntryV3Name()).eClass(); } //------------------------------------------------------------------------------------------------- // Handling templates and contexts //------------------------------------------------------------------------------------------------- /** * @param context template context, which has template ids attached to different steps * @param isMaxCardinality: if true, this method refers to max cardinality; * if false, it refers to min cardinality * @return -1 if no template in the context constrains the cardinality of the * end node. * If some rules in some templates constrain the min or max cardinality of the end node * (as defined by isMaxCardinality) to be 1, return the smallest step number from which the * cardinality is constrained to 1. */ private int cardinalityConstrainedFromStep(TemplatedPath context, boolean isMaxCardinality) { int constrainingStep = 100; for (Iterator<TemplateRule> ir = applicableRules(context); ir.hasNext();) { TemplateRule rule = ir.next(); int ruleStep = rule.getContextStep(); for (Iterator<TemplateAssertion> it = rule.constrainingAssertions().iterator();it.hasNext();) { TemplateAssertion ta = it.next(); if ((isMaxCardinality) && (ta.testNode().finalNodeMustBeSingle(context, ruleStep))) constrainingStep = ruleStep; else if ((!isMaxCardinality) && (ta.testNode().finalNodeMustExist(context, ruleStep))) constrainingStep = ruleStep; } } if (constrainingStep == 100) constrainingStep = -1; // no constraints were found return constrainingStep; } /** * @param context a template context (= path from the CDA root, with constraints) * @return true if there are any templates on nodes which are descendants * of the leaf node of the context (not the leaf node itself) */ private boolean moreTemplatesInSubtree(TemplatedPath context) throws MapperException { boolean some = false; for (Iterator<CDATemplate> it = allTemplates(); it.hasNext();) { CDATemplate template = it.next(); for (Iterator<TemplatedPath> ic = template.extendingContexts(context).iterator();ic.hasNext();) if (ic.next().length() > context.length()) some = true; } return some; } /** * @param context a template context * @return all templates compatible with the contexts. * The outer Vector is a Vector of mutually exclusive sets of templates. * Templates in one of the inner Vectors can co-exist on the same node. * (Two templates cannot co-exist on the same node unless one of them * is declared in the template usage file to constrain the other) */ private Vector<Vector<CDATemplate>> templateSetsForContext(TemplatedPath context) throws MapperException { // find all templates for the context, irrespective of which ones can coexist on one node Vector<CDATemplate> ungrouped = new Vector<CDATemplate>(); for (Iterator<CDATemplate> it = allTemplates(); it.hasNext();) { CDATemplate temp = it.next(); if (temp.isCompatibleWithContext(context)) ungrouped.add(temp); } // group the templates into sets that can co-exist on the same node if (ungrouped.size() == 0) return new Vector<Vector<CDATemplate>>(); return groupTemplates(ungrouped); } /** * For the top context (ClinicalDocument node) all templates which are allowed * by the template usage file must appear; so there is only one group of * mutually compatible templates * @param context * @return * @throws MapperException */ private Vector<CDATemplate> templatesForTopContext(TemplatedPath context) throws MapperException { // find all templates for the context, irrespective of which ones can coexist on one node Vector<CDATemplate> ungrouped = new Vector<CDATemplate>(); for (Iterator<CDATemplate> it = allTemplates(); it.hasNext();) { CDATemplate temp = it.next(); if (temp.isCompatibleWithContext(context)) ungrouped.add(temp); } return ungrouped; } /** * @param ungrouped a flat Vector of templates * @return Vector of groups of templates that can coexist on one node */ private Vector<Vector<CDATemplate>> groupTemplates(Vector<CDATemplate> ungrouped) throws MapperException { //trace("Grouping " + ungrouped.size()); Vector<Vector<CDATemplate>> grouped = new Vector<Vector<CDATemplate>>(); if (ungrouped.size() > 10) return groupSectionTemplates(ungrouped); else { // subsets becomes 2**N int subsets = 1; for (int i = 0; i < ungrouped.size(); i++) subsets = 2*subsets; // j ranges from 1 to 2**N - 1 for (int j=1; j < subsets; j++) { Vector<CDATemplate> testSet = makeSubset(j, ungrouped); if (isViable(testSet)) grouped.add(testSet); } } //trace("Groups found: " + grouped.size()); return grouped; } /** * * @param j an integer ranging from 1 to 2**N - 1; it has N bits of 0 or 1 * @param ungrouped a Vector of N templates * @return a subset of the Vector, using the bits of j to select templates */ private Vector<CDATemplate> makeSubset(int j, Vector<CDATemplate> ungrouped) { Vector<CDATemplate> subset = new Vector<CDATemplate>(); int index = j; for (int i = 0; i < ungrouped.size(); i++) { int rounded = (index/2)*2; if (index == rounded + 1) subset.add(ungrouped.get(i)); index = index/2; } return subset; } /** * @param testSet * @return true if the test set of templates is viable. This means that * for every template in the set: * (a) it is compatible with every other template in the set * (b) every other template that it constrains is in the set */ private boolean isViable(Vector<CDATemplate> testSet) throws MapperException { for (int i = 0; i < testSet.size(); i++) { CDATemplate temp = testSet.get(i); if (!hasAllConstrained(temp,testSet)) return false; for (Iterator<CDATemplate> iu = testSet.iterator();iu.hasNext();) if (!temp.canBeOnSameNodeAs(iu.next())) return false; } return true; } /** * @param temp * @param testSet * @return true if the test set contains all the templates that temp constrains */ private boolean hasAllConstrained(CDATemplate temp, Vector<CDATemplate> testSet) throws MapperException { for (Iterator<CDATemplate> it = temp.constrainedTemplates().iterator();it.hasNext();) { CDATemplate consTemp = it.next(); boolean found = false; for (Iterator<CDATemplate> iu = testSet.iterator();iu.hasNext();) if (iu.next().fullTemplateId().equals(consTemp.fullTemplateId())) found = true; if (!found) return false; } return true; } /** * @param ungrouped the set of all section templates * @return all groups of section templates that can coexist on the same node * Each group consists only of one template and all the others it constrains, * directly or indirectly * @throws MapperException */ private Vector<Vector<CDATemplate>> groupSectionTemplates(Vector<CDATemplate> ungrouped) throws MapperException { Vector<Vector<CDATemplate>> groups = new Vector<Vector<CDATemplate>>(); for (Iterator<CDATemplate> it = ungrouped.iterator(); it.hasNext();) { CDATemplate temp = it.next(); if (hasAllConstrained(temp, ungrouped)) { Vector<CDATemplate> group = new Vector<CDATemplate>(); group.add(temp); for (Iterator<CDATemplate> iu = temp.constrainedTemplates().iterator();iu.hasNext();) group.add(iu.next()); groups.add(group); } } return groups; } /** * @param temp * @param soFar * @return true of the template temp is anywhere in the Vector of Vectors of templates */ @SuppressWarnings("unused") private boolean alreadyFound(CDATemplate temp, Vector<Vector<CDATemplate>> soFar) { String id = temp.fullTemplateId(); boolean found = false; for (int outer = 0; outer < soFar.size(); outer++) { Vector<CDATemplate> set = soFar.get(outer); for (int inner = 0; inner < set.size(); inner++) if (id.equals(set.get(inner).fullTemplateId())) found = true; } return found; } //--------------------------------------------------------------------------------------------------- // Trivia //--------------------------------------------------------------------------------------------------- /** * write out all templates, with the short forms of their contexts */ @SuppressWarnings("unused") private void writeAllTemplates() throws MapperException { System.out.println("All Templates"); for (Iterator<CDATemplate> it = allTemplates();it.hasNext();) { CDATemplate temp = it.next(); System.out.println("Template " + temp.fullTemplateId()); for (Iterator<TemplatedPath> ic = temp.allContexts().iterator();ic.hasNext();) { TemplatedPath con = ic.next(); System.out.println(con.shortStringForm()); } } } private void trace(String s) {if (tracing()) System.out.println(s);} }