package com.openMap1.mapper.health.cda; import java.util.Iterator; import java.util.Vector; import org.eclipse.emf.ecore.EClass; import org.w3c.dom.Element; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.util.ModelUtil; import com.openMap1.mapper.util.XMLUtil; /** * A rule within a template, which may make several assertions * @author robert * */ public class TemplateRule { public boolean tracing() {return template.tracing();} public static String CDA_PREFIX = "cda"; public String contextString() {return contextString();} private String contextString; public TestNode contextFromTemplate() {return contextFromTemplate;} private TestNode contextFromTemplate; private String templateId; /** * @return assertions which define which node this template can appear on */ public Vector<TemplateAssertion> definingAssertions() {return definingAssertions;} private Vector<TemplateAssertion> definingAssertions; /** * @return assertions which constrain the structure of the tree below the node of the template */ public Vector<TemplateAssertion> constrainingAssertions() {return constrainingAssertions;} private Vector<TemplateAssertion> constrainingAssertions; /** * @return the step 0..N of a context at which this rule applies. * This needs to be set using setContextStep when the rule is matched with a context; * it usually has only one possible value, but may have more if its template * can appear at different depths */ public int getContextStep() {return contextStep;} public void setContextStep(int step) {contextStep = step;} private int contextStep; private CDATemplate template; //-------------------------------------------------------------------------------------- // Constructor //-------------------------------------------------------------------------------------- public TemplateRule(CDATemplate template, String templateId, Element ruleEl) throws MapperException { this.template = template; this.templateId = templateId; try { contextString = ruleEl.getAttribute("context"); contextFromTemplate = new TestNode(contextString); if (isStandardContext()) trace ("Rule with standard context"); else if (extendsStandardContext()) trace ("Rule extends standard context: '" + contextString + "' " + contextFromTemplate.structure()); else trace("Rule with non-standard context '" + contextString + "' " + contextFromTemplate.structure()); } catch (Exception ex) {trace("Failed to parse context '" + contextString + "' in template '" + template.getLocalId() + "' : " + ex.getMessage());} definingAssertions = new Vector<TemplateAssertion>(); constrainingAssertions = new Vector<TemplateAssertion>(); Vector<Element> assertEls = XMLUtil.namedChildElements(ruleEl, "assert"); for (Iterator<Element> it = assertEls.iterator();it.hasNext();) { TemplateAssertion ta = new TemplateAssertion(this,it.next()); if (ta.isNodeDefining()) definingAssertions.add(ta); else constrainingAssertions.add(ta); } } //----------------------------------------------------------------------------------------------- // rule contexts and applicability //----------------------------------------------------------------------------------------------- /** * @param TemplatedPath a context in which this rule's template is on a step * @param templateStep the step number on which the template appears * @return true if the rule applies in the context, i.e. * either the rule has the same standard context as the template * or the rule extends the context of the template, and the extension * is compatible with the rest of the supplied context. * As a side-effect, set the rule's context step. */ public boolean matchesContext(TemplatedPath context, int templateStep) { if (isStandardContext()) { // side-effect; note that the rule step is the same as the template step setContextStep(templateStep); return true; } else if ((extendsStandardContext()) && (contextFromTemplate.connector() == TestNode.XPATH)) { int ruleSteps = contextFromTemplate.childNodes().size(); // 2 or more // the rule cannot apply if its context XPath is too long for the supplied context if ((templateStep + ruleSteps) > context.length()) return false; boolean matches = true; // the first step of the rule context merely checks the template id; must pass for (int r = 1; r < ruleSteps; r++) { TestNode ruleStep = contextFromTemplate.childNodes().get(r); ContextStep contextStep = context.step(r); if (!ruleStep.isCompatible(contextStep)) matches = false; } // side-effect; note that the rule step is greater than the template step if (matches) setContextStep(templateStep + ruleSteps - 1); return matches; } return false; } /** * * @param context * @param ruleStep * @return true if this rule implies some constraint on a node * in the subtree below the final node of the context */ public boolean constrainsSubtreeBelow(TemplatedPath context, int ruleStep) { boolean constrains = false; for (Iterator<TemplateAssertion> it = constrainingAssertions.iterator();it.hasNext();) { TemplateAssertion assertion = it.next(); if (assertion.testNode().constrainsSubtreeBeneath(context, ruleStep)) constrains = true; } return constrains; } /** * @return true if the context of this rule is the standard' context which just * says that the appropriate <templateId> Element is a child of the node. * This test does not yet allow for spaces around the '='. */ public boolean isStandardContext() { return (contextString.equals(standardContext())); } public boolean extendsStandardContext() { return (contextString.startsWith(standardContext())); } /** * @return the 'standard' context string for a template, of * the form '*[cda:templateId/@root="<template id>"]', * which picks out the template node */ private String standardContext() {return ("*[" + CDA_PREFIX + ":templateId/@root=\"" + templateId + "\"]");} //-------------------------------------------------------------------------------------------- // annotating the Ecore model with template constraints //-------------------------------------------------------------------------------------------- /** * add annotations for each constraint the template puts on descendant nodes * @param context - the association path to the template class (not used) * @param templatedClass the EClass to receive the annotations * @param suffix a String "", or "_1", "_2" etc defining which of the templates * on the EClass provides each constraint */ public void addRuleConstraintAnnotations(TemplatedPath context,EClass templatedClass,String suffix) { for (Iterator<TemplateAssertion> it = constrainingAssertions.iterator();it.hasNext();) { TemplateAssertion assertion = it.next(); TestNode testNode = assertion.testNode(); Vector<AttributeValueConstraint> avcs = new Vector<AttributeValueConstraint>(); // trace("\nFor " + templatedClass.getName() + " assert: " + assertion.test() + "\nStructure " + testNode.structure()); /* find the attribute value constraints in two cases (a) rule has same context as template * and (b) rule context is an extension of template context. The two could be united easily. */ if (isStandardContext()) { avcs = testNode.getStringValueConstraints(); } else if (extendsStandardContext()) { //trace("***Extension " + contextString); avcs = testNode.getStringValueConstraints(contextFromTemplate); } //put each attribute value constraint on the template EClass for (int a = 0; a < avcs.size(); a++) { AttributeValueConstraint avc = avcs.get(a); // do not constrain templateId root attributes; will be done later if (!avc.templateIdInPath()) { // System.out.println("Constraint from " + templatedClass.getName() + ": " + avc.stringForm()); String constraintKey = "constraint" + suffix + ":" + avc.path(); ModelUtil.addMIFAnnotation(templatedClass, constraintKey, avc.value()); } } } } //-------------------------------------------------------------------------------------------- // trivia //-------------------------------------------------------------------------------------------- private void trace(String s) {if (tracing()) System.out.println(s);} }