package com.openMap1.mapper.writer; import com.openMap1.mapper.mapping.*; import com.openMap1.mapper.core.*; import com.openMap1.mapper.reader.*; import com.openMap1.mapper.util.*; import com.openMap1.mapper.util.Timer; import java.util.*; import org.w3c.dom.*; /** * procedures for writing XML. * Subclasses of inner class 'step' define 12 types of step within procedures. * * */ public class WProc { // issues detected at runtime protected Hashtable<String,Hashtable<String,RunIssue>> runIssues; /* the Xpth specifying what nodes this procedure may be called for; if this is a definite path, rootPath must be equal to it. In all cases, rootPath must be compatible with it.*/ protected Xpth pathSpec; protected Xpth rootPath; // definite path from the root to the current element private Vector<ClassSet> expectedContextCSets = new Vector<ClassSet>(); // classSets of context objects expected at code generation /* if true, do this procedure only on creating an element; if false, only do it on revisiting the element. */ protected boolean onCreate; protected Vector<whenValue> whenValues; // values of when-conditions defined when this procedure executes protected Vector<step> steps; // vector of step objects for successive steps of the procedure protected int stepNo; // step currently executing protected ProcedureClass XW; private ProcedureUser XU; // XML writer which created this procedure private MappedXMLWriter XT; protected XMLOutputFile xout; // XML document being written public void setOutputFile(XMLOutputFile xout) {this.xout = xout;} private objectGetter oGet; // to get object information to write out private Element currentEl; // current element for a runtime instance of the procedure private String currentString; // current string, set to property values and put in elements and attributes protected subtreeContext context; // subtree context for the runtime instance procedure private boolean grouping = false; //whether there are any grouping criteria in force private Vector<groupingCriterion> groupingCriteria = new Vector<groupingCriterion>(); // Vector of groupingCriterion objects private Vector<Vector<String>> groupingVectors; // runtime values of groups for the grouping criteria private Vector<Element> groupEls; // elements put in output XML for groups protected codingContext creationContext; // only used for clone() protected MDLXOReader inputReader; // Definitions of input MDL for XSLT generation public Xpth pathSpec() {return pathSpec;} public Xpth rootPath() {return rootPath;} public boolean onCreate() {return onCreate;} public void setCurrentElement(Element el) {currentEl = el;} public void setContext(subtreeContext con) {context = con;} public void setRootPath(Xpth rp) {rootPath = rp;} public void addExpectedContextCSet(ClassSet cs) {expectedContextCSets.addElement(cs);} private Timer timer; protected boolean tracing = false; //------------------------------------------------------------------------------------------ // instance variables used for XSLT generation //------------------------------------------------------------------------------------------ protected XSLOutputFile xslout; // XML document being written protected XSLGenerator XX; protected String currentStringVar; // in XSLT, the name of the variable which will hold the current string // variable names (named after classes in context) either used in this template or passed by it to lower templates protected Hashtable<String, String> contextVariablesUsed = new Hashtable<String, String>(); /* variables used in this template (or passed down as parameters to lower templates) which are not declared locally in this template, but must be passed into it as parameters */ public Hashtable<String, String> parametersNeeded = new Hashtable<String, String>(); protected Element templateNode; // top element of the XSLT template associated with this procedure protected Element outputElement; // element to be output by the XSLT template associated with this procedure protected Element nodeForXSLT; // the node that further XSLT in this template is to be appended to protected String matchPattern = null; // value of attribute 'match' for the XSLT template generated by this procedure protected String modeString = null; // value of the attribute 'mode' for the XSLT template generated by this procedure protected String createdElementName = null; // tag name of the element created by the XSLT template for this procedure protected String nameString = null; //value of the attribute 'name' for the XSLT template written by this procedure //--------------------------------------------------------------------------- // Constructor //--------------------------------------------------------------------------- // create an empty procedure public WProc(Xpth ps, ProcedureClass xwrit, codingContext cc, boolean create) { pathSpec = ps; XW = xwrit; if (xwrit instanceof ProcedureUser) { XU = (ProcedureUser)xwrit; xout = XU.xout(); if (xout instanceof XSLOutputFile) xslout = (XSLOutputFile)xout; oGet = XU.oGet(); if (xwrit instanceof MappedXMLWriter) XT = (MappedXMLWriter)xwrit; if (xwrit instanceof XSLGenerator) XX = (XSLGenerator)xwrit; } onCreate = create; /* this vector is filled out with actual steps at compile time and never changed at run time. */ steps = new Vector<step>(); stepNo = -1; currentString = ""; creationContext = cc; // take the when-condition values defining this procedure from the context when it was created whenValues = new Vector<whenValue>(); if (cc != null) // currently cc is null for WProcs read in from the XML form { for (Enumeration<whenValue> en = cc.whenValues().elements(); en.hasMoreElements();) {whenValues.addElement(en.nextElement());} expectedContextCSets = ClassSet.vCopy(cc.uniqueCSets()); } timer = new Timer("Wproc"); } /** * Give this WProc a timer, so the times it takes for different operations * can be reported alongside other times. */ public void giveTimer(Timer newTimer, boolean addTimes) { if ((addTimes) && (timer != null)) newTimer.addTimes(timer); timer = newTimer; } // constructor from a <proc> element and its subtree public WProc(Xpth ps, ProcedureClass xwrit, Element procEl) throws MapperException { pathSpec = ps; XW = xwrit; whenValues = new Vector<whenValue>(); steps = new Vector<step>(); // read procedure type String type = procEl.getAttribute("type"); if (type.equals("create")) {onCreate = true;} else if (type.equals("revisit")) {onCreate = false;} else fatalMessage("Invalid type '" + type + "' for procedure at path " + pathSpec.stringForm()); // give it a coding context, as it would have if it had just been compiled creationContext = new codingContext(XW,ps,false); // no code tracing now // read context classSets, if provided expectedContextCSets = new Vector<ClassSet>(); Element contextEl = XMLUtil.firstNamedChild(procEl,"context"); if (contextEl != null) { Vector<Element> cClasses = XMLUtil.namedChildElements(contextEl,"ClassSet"); for (int i = 0; i < cClasses.size(); i++) { Element classEl = cClasses.elementAt(i); expectedContextCSets.addElement(new ClassSet(classEl)); creationContext.addUniqueCSet(new ClassSet(classEl)); } } // read when-values whenValues = new Vector<whenValue>(); Element whensEl = XMLUtil.firstNamedChild(procEl,"whenConditions"); if (whensEl != null) { Vector<Element> whens = XMLUtil.namedChildElements(whensEl,"when"); for (int i = 0; i < whens.size(); i++) { Element whenEl = (Element)whens.elementAt(i); whenValue wv = new whenValue(whenEl,XW.NSSet()); whenValues.addElement(wv); creationContext.setWhenValue(wv); } } // read steps steps = new Vector<step>(); Vector<Element> stepEls = XMLUtil.namedChildElements(procEl,"step"); for (int i = 0; i < stepEls.size(); i++) { Element stepEl = (Element)stepEls.elementAt(i); step st = makeStep(stepEl); if (st != null) steps.addElement(st); } timer = new Timer("Wproc"); } /** * (this is done when merging imported WProc files with the WProcs that import them) * replace every occurrence of some old [class, subset] by a new one * @param oldCSet the [class,subset] to be replaced * @param newCSet the new [class, subset] to replace it */ public void replaceClassSet(ClassSet oldCSet, ClassSet newCSet) { // replace classSets in when values (need to do in creation context as well?) for (Iterator<whenValue> it = whenValues.iterator();it.hasNext();) it.next().replaceClassSet(oldCSet, newCSet); // replace classSets in context classes for (int i = 0; i < expectedContextCSets.size(); i++) { ClassSet cSet = expectedContextCSets.get(i); if (cSet.equals(oldCSet)) expectedContextCSets.set(i, newCSet); } // replace classSets in steps of the procedure for (int i = 0; i < this.nSteps(); i++) stepNumber(i).replaceClassSet(oldCSet,newCSet); } /** * (this is done when merging imported WProc files with the WProcs that import them) * take all when-values from the WProc which imported the WProc file of this WProc, * and add them to this WProc * @param outerWProc */ public void takeWhenValues(WProc outerWProc) throws MDLWriteException { for (Iterator<whenValue> it = outerWProc.whenValues.iterator();it.hasNext();) { whenValue wv = it.next(); whenValues.add(wv); creationContext.setWhenValue(wv); } } /** * if outerWProc has any steps other than a FillElement step, * add them in to this WProc before its own steps. * @param outerWProc a WProc read from the .wproc file that imports this one * through a FillElement step */ public void preAppendSteps(WProc outerWProc) { int outerSteps = outerWProc.steps.size(); // if there are any steps other than the FillElement step if (outerSteps > 1) { // keep this WProc's steps before overwriting them. Vector<step> ownSteps = steps; steps = new Vector<step>(); // copy across steps from the outer WProc int added = 0; for (int i = 0; i < outerSteps;i++) { step outerStep = outerWProc.stepNumber(i); if (!(outerStep instanceof fillElementStep)) { steps.add(outerStep); added++; } } // add this WProc's own steps, with higher step numbers for (int i = 0; i < ownSteps.size(); i++) { ownSteps.get(i).stepNumber = i + added; steps.add(ownSteps.get(i)); } } } /** * * @return a Vector of [tag name, class name] pairs from association instance steps * or allObjectSteps in this WProc. * The class must be represented in the input for the element to be created. * AddElement steps are stored with a class "", which requires nothing to be represented in the input */ public Vector<String[]> tagConditions() { Vector<String[]> groups = new Vector<String[]>(); for (int s = 0; s < nSteps();s++) { if (stepNumber(s) instanceof allObjectStep) { String[] pair = new String[2]; allObjectStep aos = (allObjectStep)stepNumber(s); pair[0] = aos.tagName; pair[1] = aos.cSet.className(); groups.add(pair); } else if (stepNumber(s) instanceof associationInstanceStep) { String[] group = new String[4]; associationInstanceStep ast = (associationInstanceStep)stepNumber(s); group[0] = ast.tagName; group[1] = ast.cSet1.className(); group[2] = ast.cSet2.className(); group[3] = ast.assocName; groups.add(group); } else if (stepNumber(s) instanceof addElementStep) { String[] singleton = new String[1]; addElementStep aes = (addElementStep)stepNumber(s); singleton[0] = aes.tagName; groups.add(singleton); } } return groups; } //--------------------------------------------------------------------------- // Utility methods //--------------------------------------------------------------------------- // write out the procedure to a text file public void writeProcedure() throws MapperException { String joiner = "when"; String type = "newly created"; if (!onCreate) {type = "revisited";} String none = ""; if (nSteps() == 0) none = "No "; message(""); message(none + "Procedure run on " + type + " elements with path: "); message("'" + pathSpec.stringForm() + "'"); for (int i = 0; i < whenValues.size(); i++) { whenValue wv = whenValues.elementAt(i); if (i > 0) {joiner = "and";} message(joiner + " " + wv.rootPath().stringForm() + " = '" + wv.value() + "'"); } Hashtable<String, ClassSet> uniqueContextCsets = new Hashtable<String, ClassSet>(); for (int i = 0; i < expectedContextCSets.size(); i++) { ClassSet ecs = expectedContextCSets.elementAt(i); uniqueContextCsets.put(ecs.stringForm(),ecs); } String cSets = "Expected context classes: "; for (Enumeration<String> en = uniqueContextCsets.keys();en.hasMoreElements();) {cSets = cSets + " " + en.nextElement();} message(cSets); if (nSteps() > 0) { message("------ start -------"); for (int i = 0; i < nSteps(); i++) {stepNumber(i).writeStep();} message("------- end --------"); } } /* clone a procedure to make a fresh runtime version with the same set of steps. */ public WProc pClone() throws MapperException{ WProc wpc = new WProc(pathSpec, XW, creationContext, onCreate); for (int i = 0; i < steps.size(); i++) { wpc.steps.addElement(wpc.copyStep(stepNumber(i))); wpc.stepNumber(i).enabled = stepNumber(i).enabled; } return wpc; } public step stepNumber(int i) { step res = null; if ((i < 0)| (i > steps.size() - 1)) {message("Invalid step number " + i + " in procedure '" + pathSpec.stringForm() + "'.");} else res = steps.elementAt(i); return res; } /* True if every when-condition value in this procedure is the same as the value set in the subtree context stc; - i.e if this procedure is appropriate to run in that subtree context. False if any mismatch values are found. Write an error message if any when-condition root paths are not found in the subtree context. */ boolean matchWhenValues(subtreeContext stc) { boolean res = true; for (int i = 0; i < whenValues.size(); i++) { whenValue wv = whenValues.elementAt(i); if (!stc.matchesWhenValue(wv)) res = false; } return res; } /* All the procedures that can have the same path specification are uniquely identified by this string, which notes the create/revisit distinction and the different possible when-condition values. */ public String whenValueString() { String res = "Create$"; if (!onCreate) res = "Revisit$"; for (int i = 0; i < whenValues.size(); i++) { whenValue wv = whenValues.elementAt(i); res = res + "$" + wv.value(); } return res; } // Execute this procedure, by executing all its steps in sequence, as long as they succeed. boolean executeProcedure(Hashtable<String,Hashtable<String,RunIssue>> runIssues) throws MapperException { boolean success; int i; this.runIssues = runIssues; success = true; runTraceMessage(""); runTraceMessage("Executing procedure for " + pathSpec.stringForm()); for (i = 0; i < whenValues.size(); i++) { whenValue wv = whenValues.elementAt(i); runTraceMessage("with when-value '" + wv.value() + "' at node " + wv.rootPath().stringForm()); } if (context == null) { int nature = RunIssue.RUN_CONTEXT; runWarningMessage(nature, "No runtime context for procedure"); } else if (context.rootPath() == null) { int nature = RunIssue.RUN_ROOT_PATH; runWarningMessage(nature, "No runtime root path"); } else {rootPath = context.rootPath();} for (i = 0; i < steps.size(); i++) if (success) { try{ stepNo = i; if (stepNumber(i).enabled) { if (XW.runTracing()) {stepNumber(i).writeStep();} int timerClock = stepNumber(i).stepTypeIndex; timer.start(timerClock); success = success & stepNumber(i).executeStep(); timer.stop(timerClock); if (success) {stepNumber(i).hasExecuted = true;} } } catch (RunTranslateException e) // do not catch null pointer exceptions, so there is a stack trace { if (e.issue().isFatal()) // fail (which stops execution) { stepNumber(i).writeStep(); String elName = ""; try {elName = rootPath.innerStepString();} catch (Exception ex){} // these will accumulate to make a sort of stack trace fatalMessage(elName + "[" + i + ":" + stepNumber(i).subStep + "]/" + e.getMessage()); success = false; } } // Exceptions thrown from the reader catch (notRepresentedException ex) { /* when something required for output is not represented in the input XML, * there is no point in issuing a warning; because it will happen very frequently */ /* int nature = RunIssue.RUN_REPRESENTED; try {runWarningMessage(nature,"Missing in input: " + ex.getMessage());} catch (RunTranslateException ey) {} */ } catch (PropertyConversionException ex) { int nature = RunIssue.RUN_PROPERTY_CONVERSION; try {runWarningMessage(nature,"Property Conversion: " + ex.getMessage());} catch (RunTranslateException ey) {} } } // end of loop over steps // remove all child elements which are empty removeEmptyChildElements(); runTraceMessage("Completed procedure for " + pathSpec.stringForm()); runTraceMessage(""); return success; } /** * Iterate over all child elements of the current element, which have * just been made by executing this procedure and its nested procedures, * and remove any that are empty. */ private void removeEmptyChildElements() { if (currentEl != null) for (Iterator<Element> ie = XMLUtil.childElements(currentEl).iterator();ie.hasNext();) { Element child = ie.next(); if (isEmpty(child)) { runTraceMessage("Removing empty child element '" + XMLUtil.getLocalName(child) + "' in procedure for " + pathSpec.stringForm()); currentEl.removeChild(child); } } } /** * @param el an Element * @return true if the Element is empty, i.e if it has no attributes, no text, * and all its child elements are empty. */ private boolean isEmpty(Element el) { boolean empty = (el.getAttributes().getLength() == 0); NodeList nl = el.getChildNodes(); for (int n = 0; n < nl.getLength();n++) if (empty) { Node nd = nl.item(n); if (nd instanceof Text) empty = false; if ((nd instanceof Element) && (!isEmpty((Element)nd))) empty = false; } return empty; } /* Find the string value of some property or output XML converted property ClassSet cSet refers to the output XML; objectToken oTok is an object representation in the input XML. */ String outputPropertyValue(objectToken oTok, ClassSet cSet, String propertyName) throws MapperException { timer.start(Timer.OUTPUT_PROPERTY); String res = ""; // if there is a mapping for the property in the output XML, and it is not a nonlocally converted property if ((XW.namedPropertyMappings(cSet.className(),cSet.subset(),propertyName).size() > 0) && (!XW.isPseudoProperty(cSet,propertyName))) { String unconverted = oGet.getPropertyValue(oTok,propertyName); propertyMapping pm = XW.namedPropertyMappings(cSet.className(),cSet.subset(),propertyName).get(0); // if there is a local Java property out-conversion, apply it to the un-converted value propertyConversion localConversion = pm.localOutConversion(); if ((localConversion != null)&& (localConversion.canDoJavaConvert())) { String[] args = new String[1]; args[0] = unconverted; res = localConversion.doJavaConvert(args); } // if the property mapping has a lookup table, use it in the 'out' direction else if (pm.hasLookupTable()) res = pm.getStructureValue(unconverted); // otherwise, pass through the model value to the XML else res = unconverted; } // there is a non-local 'out' property conversion to the converted property propertyName else if (XW.getOutConversion(cSet,propertyName) != null) { propertyConversion pc = XW.getOutConversion(cSet,propertyName); if ((pc.hasImplementation("Java")) && (pc.canDoJavaConvert())) { Vector<String> argVect = pc.arguments(); String[] args = new String[argVect.size()]; boolean allArgsFound = true; for (int k = 0; k < argVect.size(); k++) { String realProp = (String)argVect.elementAt(k); args[k] = oGet.getPropertyValue(oTok,realProp); if (args[k] == null) allArgsFound = false; } if (allArgsFound) {res = pc.doJavaConvert(args);} else if (!allArgsFound) { int nature= RunIssue.RUN_PROPERTY_CONVERSION; String problem = "Property out-conversion to converted property " + cSet.stringForm() + ":" + propertyName + " failed for lack of some argument."; runWarningMessage(nature,problem); } } else { int nature= RunIssue.RUN_PROPERTY_CONVERSION; String problem = "Property out-conversion for converted property " + cSet.stringForm() + ":" + propertyName + " has no accessible Java implementation."; runWarningMessage(nature,problem); } } else { int nature= RunIssue.RUN_PROPERTY_CONVERSION; String problem = "Found no mapping or property conversion for property " + cSet.stringForm() + ":" + propertyName; runWarningMessage(nature,problem); } timer.stop(Timer.OUTPUT_PROPERTY); return res; } /* create a new empty element in an XMLFile xf. This may be an elementImpl if the tag name has no namespace prefix, or an Element NSImpl if there is a namespace prefix. In the latter case, check it is one of the allowed prefixes of the XMLFile. If the namespace set has a default namespace, assume that tag names with no prefix are all in the default namespace. */ Element newElement(XMLOutputFile xf, String tagName) { String prefix,localName,URI; Element el = null; prefix = NamespaceSet.prefix(tagName); localName = NamespaceSet.localName(tagName); if (prefix.equals("")) // element in default namespace or no namespace { if (xf.NSSet().hasDefault()) //element in default namespace { URI = xf.NSSet().getByPrefix("").URI(); el = xf.NSElement("", localName, URI); } else // element in no namespace { el = xf.newElement(tagName); } } else //element in a prefixed namespace { URI = xf.NSSet().getByPrefix(prefix).URI(); el = xf.NSElement(prefix, localName, URI); } return el; } Element newTextElement(XMLOutputFile xf, String tagName, String text) { return xf.textElement(tagName,text); } // optional tracing of runtime steps void runTraceMessage(String s) { if (XW.runTracing()) message(s); } /** * warnings or errors - usually not serious enough to stop running, * but the response is determined by the nature - see class RunIssue * * @throws RunTranslateException caught by executeProcedure */ void runWarningMessage(int nature, String problem) throws RunTranslateException { RunIssue ri = new RunIssue(nature,"","",problem, rootPath(),stepNo); if ((runIssues != null) && (rootPath() != null)) { Hashtable<String,RunIssue> issues = runIssues.get(rootPath().stringForm()); if (issues == null) issues = new Hashtable<String,RunIssue>(); RunIssue existingIssue = issues.get(ri.key()); if (existingIssue != null) existingIssue.addOccurrence(); else { issues.put(ri.key(), ri); runIssues.put(rootPath().stringForm(),issues); } throw new RunTranslateException(problem,ri); } else System.out.println("Untrapped runtime warning: " + problem); } /** * simple way to pick up various fatal errors * (usually internal resulting from bugs) * @param problem * @throws RunTranslateException */ void fatalMessage(String problem) throws RunTranslateException {runWarningMessage(RunIssue.RUN_FATAL_ERROR,problem);} protected void message(String s) {XW.message(s);} public int nSteps() {return steps.size();} /* return the number of steps which are not when-value steps */ public int nonWhenSteps() { int nw = 0; for (int i = 0; i < steps.size(); i++) { if (!(stepNumber(i) instanceof setWhenValueStep)) nw++; } return nw; } //--------------------------------------------------------------------------- // inner classes for different types of step //--------------------------------------------------------------------------- // superclass for all steps public abstract class step { int stepNumber; int stepTypeIndex; String stepType = "default"; // the type of the step /* if you encounter any exception in executing this step, disable it so it does not try to execute again. */ boolean enabled = true; // for retrospective analysis of execution boolean hasExecuted = false; /* the case in the code generation decision trees which generated this step, e.g "3.4.2" */ String caseNo; abstract boolean executeStep() throws MapperException; abstract boolean XSLTForStep() throws MapperException; abstract void writeStep(); String stepPrefix() {return "Procedure path '" + pathSpec.stringForm() + "'; Step " + stepNumber + " (" + caseNo +") ";} void stepMessage(String s) {message(s + " in " + stepPrefix());} int subStep = 0; // any step which sets the current string public boolean isSetTextStep() { return ((this instanceof getPropertyStep)| (this instanceof getConstantStep)| (this instanceof relatedPropertiesStep)| (this instanceof setWhenValueStep)); } // XML output of shared properties of all steps public Element writeToXML(XMLOutputFile xout) { Element stepEl = xout.newElement("step"); stepEl.setAttribute("caseNo",caseNo); stepEl.setAttribute("stepType",stepType); stepEl.setAttribute("stepNumber",IntToString(stepNumber)); return stepEl; } // XML input of shared properties of all steps void doSharedProps(Element stepEl) { caseNo = stepEl.getAttribute("caseNo"); stepType = stepEl.getAttribute("stepType"); stepNumber = StringToInt(stepEl.getAttribute("stepNumber")); } // for a correct round-trip of a null property name to the XML procedure String propNameToXML(String pName) { String res = pName; if (res == null) res = "nullPropertyName"; return res; } // for a correct round-trip of a null property name to the XML procedure String propNameFromXML(String pXName) { String res = pXName; if (res.equals( "nullPropertyName")) res = null; return res; } // to be overridden when necessary for different types of step public void replaceClassSet(ClassSet oldCSet, ClassSet newCSet) { } } // end of inner class step //---------------------------------------------------------------------------------------- // //---------------------------------------------------------------------------------------- // method to copy steps of any subclass step copyStep(step st) throws MapperException { step res=null; if (st instanceof addElementStep) {addElementStep s = (addElementStep)st; res = new addElementStep(s.tagName,s.ifString,s.stepNumber,s.caseNo);} else if (st instanceof fillElementStep) {fillElementStep s = (fillElementStep)st; res = new fillElementStep(s.mapperPath,s.className,s.subset,s.stepNumber,s.caseNo);} else if (st instanceof addToContextStep) {addToContextStep s = (addToContextStep)st; res = new addToContextStep(s.cSet1,s.assocName,s.cSet2,s.targetEnd,s.stepNumber,s.caseNo);} else if (st instanceof setTextStep) {setTextStep s = (setTextStep)st; res = new setTextStep(s.appendFlag,s.stepNumber,s.caseNo);} else if (st instanceof addAttributeStep) {addAttributeStep s = (addAttributeStep)st; res = new addAttributeStep(s.attName,s.appendFlag,s.stepNumber,s.caseNo);} else if (st instanceof getPropertyStep) {getPropertyStep s = (getPropertyStep)st; res = new getPropertyStep(s.cSet,s.pName,s.mult,s.stepNumber,s.caseNo);} else if (st instanceof getConstantStep) {getConstantStep s = (getConstantStep)st; res = new getConstantStep(s.constant,s.stepNumber,s.caseNo);} else if (st instanceof relatedPropertiesStep) {relatedPropertiesStep s = (relatedPropertiesStep)st; res = new relatedPropertiesStep(s.refCSet,s.assocChain,s.propName,s.stepNumber,s.caseNo);} else if (st instanceof setWhenValueStep) {setWhenValueStep s = (setWhenValueStep)st; res = new setWhenValueStep(s.wVal,s.stepNumber,s.caseNo);} else if (st instanceof allObjectStep) {allObjectStep s = (allObjectStep)st; res = new allObjectStep(s.cSet,s.tagName,s.specialNode,s.stepNumber,s.caseNo);} else if (st instanceof associationInstanceStep) {associationInstanceStep s = (associationInstanceStep)st; res = new associationInstanceStep(s.cSet1,s.assocName, s.cSet2,s.endsNotInContext, s.mult, s.tagName,s.stepNumber,s.caseNo);} else if (st instanceof groupingPropertyStep) {groupingPropertyStep s = (groupingPropertyStep)st; res = new groupingPropertyStep(s.cSet,s.pName,s.stepNumber,s.caseNo);} else if (st instanceof unPrimeStep) {unPrimeStep s = (unPrimeStep)st; res = new unPrimeStep(s.stepNumber,s.caseNo);} else fatalMessage("Failed to copy new step type " + st.getClass().getName()); return res; } //--------------------------------------------------------------------------- // add a single empty element below the current element class addElementStep extends step { String tagName; boolean ifString; addElementStep(String tagn, boolean ifs, int stno, String cno) { stepNumber = stno; caseNo = cno; stepType = "addElement"; stepTypeIndex = Timer.WPROC_STEP + 1; tagName = tagn; ifString = ifs; } // constructor from XML procedures file addElementStep(Element stepEl) { doSharedProps(stepEl); tagName = stepEl.getAttribute("tagName"); ifString = StringToBoolean(stepEl.getAttribute("ifString")); } // XML output to procedures file public Element writeToXML(XMLOutputFile xout) { Element stepEl = super.writeToXML( xout); stepEl.setAttribute("tagName",tagName); stepEl.setAttribute("ifString",BooleanToString(ifString)); return stepEl; } /* Create a new element with this tag name, add it as the next child of the current element, and do the procedure for the new element. If the current element is null, the new element becomes the root element of the document. But do nothing if ifString = true and the current string is empty. */ boolean executeStep() throws MapperException { Element el; Xpth newPath; boolean success = true; /* when ifString is true, all actions are only done if the current string is non-empty. */ if ((!ifString)|(!(currentString.equals("")))) { el = newElement(xout,tagName); subStep = 1; // add the element to the current element addNewElement(el); subStep = 2; newPath = rootPath.addInnerStep(tagName); subStep = 3; subtreeContext newContext = context.copySC(); newContext.setRootPath(newPath); subStep = 4; // call a procedure for the newly created element if (el == null) { int nature = RunIssue.RUN_NULL_ELEMENT; runWarningMessage(nature,"Null element"); success = false; } else {success = XT.callProcedure(stepTypeIndex, el,true,newPath,newContext,runIssues);} } return success; } boolean XSLTForStep() throws MapperException // XSLT for addElementStep { boolean res = true; if (ifString && (currentStringVar == null)) {} // do nothing if this step depends on a current String which is null // (eg if a property is not represented in the input XML) else { /* generate a call to a template which will add the element. Name it after the element which will be added, with a unique number suffix.*/ Element call = XSLElement("call-template"); subStep = 1; String tName = tagName + XX.newName(); subStep = 2; call.setAttribute("name", tName); subStep = 3; // create the template which will be called Xpth newPath = rootPath.addInnerStep(tagName); subStep = 5; subtreeContext newContext = context.copySC(); newContext.setRootPath(newPath); subStep = 6; /* If there is no procedure for the node, need to do nothing silently. */ try { // this will throw an exception if there is no procedure WProcXSLT elProc = (WProcXSLT)XX.findProcedure(true, newPath, newContext); elProc.setContext(newContext); elProc.setRootPath(newPath); elProc.setNameString(tName); elProc.setCreatedElementName(tagName); res = elProc.generateXSLT(null); // add <with-param> elements to the template call as needed addParametersToCallOrApply(call,elProc); /* when ifString is true, all actions are only done if the current string is non-empty. */ if (ifString) { Element ifEl = XSLElement("if"); ifEl.setAttribute("test", "string($" + currentStringVar + ")"); nodeForXSLT.appendChild(ifEl); ifEl.appendChild(call); } else if (!ifString) { nodeForXSLT.appendChild(call); subStep = 4; } } catch (MapperException e) {fatalMessage("Failed to find WProc for addElement step");} } return res; } void writeStep() { String coda = ""; if (ifString) {coda = ", if the current string is not empty";} message(stepPrefix() + ": add empty element '" + tagName + "' " + coda + "."); } } // method to add this step to the procedure public boolean as_AddEl(String tagName,boolean ifs,String caseNo) { int step = steps.size(); steps.addElement(new addElementStep(tagName,ifs,step,caseNo)); return true; } //--------------------------------------------------------------------------- // fill the current element with structure from an imported XMLWriter class fillElementStep extends step { String mapperPath; String className; String subset; fillElementStep(String mapperPath, String className, String subset, int stno, String cno) { stepNumber = stno; caseNo = cno; stepType = "fillElement"; stepTypeIndex = Timer.WPROC_STEP + 2; this.mapperPath = mapperPath; this.className = className; this.subset = subset; } // constructor from XML procedures file fillElementStep(Element stepEl) { doSharedProps(stepEl); mapperPath = stepEl.getAttribute("mapperPath"); className = stepEl.getAttribute("className"); subset = stepEl.getAttribute("subset"); } // if the class Set matches an old classSet, change it to a new one public void replaceClassSet(ClassSet oldCSet, ClassSet newCSet) { if ((className.equals(oldCSet.className())) && (subset.equals(oldCSet.subset()))) { className = newCSet.className(); subset = newCSet.subset(); } } // XML output to procedures file public Element writeToXML(XMLOutputFile xout) { Element stepEl = super.writeToXML( xout); stepEl.setAttribute("mapperPath",mapperPath); stepEl.setAttribute("className",className); stepEl.setAttribute("subset",subset); return stepEl; } /* * Find the XMLWriter for the mapping set at location mapperPath, * and pass the current element to it to be extended by that XMLWriter. * * Pass the writer the objectToken of the class 'classname' and subset 'subset' * found in the current context. */ boolean executeStep() throws MapperException { boolean success = true; XMLWriter calledWriter = XT.getImportedXMLWriter(mapperPath); objectToken passedObject = context.getObjectTokenByOutputClassSet(new ClassSet(className,subset)); if (passedObject == null) throw new MapperException("Cannot find an object of class '" + className + "' and subset '" + subset + "' in the context."); currentEl = calledWriter.extendXMLDOM(currentEl, passedObject); return success; } /** * Do nothing; elsewhere, generate XSLT for imported WProc files */ boolean XSLTForStep() throws MapperException // XSLT for fillElementStep { // throw new MapperException("XSLT generation does not yet handle fillElementStep"); return true; } void writeStep() { message(stepPrefix() + ": fill element using mappings at '" + mapperPath + "' using an object of class '" + className + "'."); } } // method to add this step to the procedure public boolean as_FillEl(String mapperPath, String className,String subset, String caseNo) { int step = steps.size(); steps.addElement(new fillElementStep(mapperPath,className,subset, step,caseNo)); return true; } //--------------------------------------------------------------------------- /* navigate the association to end targetEnd, from an object at the other end which is in the context object set, finding only one object at the target end and adding it to the context object set. */ class addToContextStep extends step { ClassSet cSet1; ClassSet cSet2; ClassSet refCSet,tarCSet; String assocName; int targetEnd; // = 1 or 2 int startEnd; multiplicity mult; // multiplicity expectation is always (1..1) addToContextStep(ClassSet cs1, String aName, ClassSet cs2, int te, int stno, String cno) throws RunTranslateException { cSet1 = cs1; assocName = aName; cSet2 = cs2; if ((te == 1)|(te== 2)) {targetEnd = te;} else fatalMessage("Invalid target end for addToContextStep: " + te); mult = new multiplicity(true,true); stepNumber = stno; caseNo = cno; stepType = "addToContext"; stepTypeIndex = Timer.WPROC_STEP + 3; setRefAndTarget(); } private void setRefAndTarget() { if (targetEnd == 1) { startEnd = 2; refCSet = cSet2; tarCSet = cSet1; } else if (targetEnd == 2) { startEnd = 1; refCSet = cSet1; tarCSet = cSet2; } } // if a classSet matches an old classSet, change it to a new one public void replaceClassSet(ClassSet oldCSet, ClassSet newCSet) { if (cSet1.equals(oldCSet)) cSet1 = newCSet; if (cSet2.equals(oldCSet)) cSet2 = newCSet; if (refCSet.equals(oldCSet)) refCSet = newCSet; if (tarCSet.equals(oldCSet)) tarCSet = newCSet; } // constructor from XML procedures file addToContextStep(Element stepEl) { doSharedProps(stepEl); cSet1 = new ClassSet(XMLUtil.firstNamedChild(stepEl,"classSet1")); cSet2 = new ClassSet(XMLUtil.firstNamedChild(stepEl,"classSet2")); assocName = stepEl.getAttribute("assocName"); targetEnd = StringToInt(stepEl.getAttribute("targetEnd")); mult = new multiplicity(true,true); setRefAndTarget(); } // XML output to procedures file public Element writeToXML(XMLOutputFile xout) { Element stepEl = super.writeToXML( xout); stepEl.appendChild(cSet1.XMLOut("classSet1",xout)); stepEl.appendChild(cSet2.XMLOut("classSet2",xout)); stepEl.setAttribute("assocName",assocName); stepEl.setAttribute("targetEnd",IntToString(targetEnd)); return stepEl; } /* navigate the association to end targetEnd, from an object at the other end which is in the context object set, finding only one object at the target end and adding it to the context object set. If the source-end object is not in the context object set, do nothing. If the link association is not represented or gives no target-end objects, do nothing. If the link association gives more than one target-end object, use only the first. */ boolean executeStep() throws MapperException { boolean res = false; objectToken refOToken,tarOToken; Vector<objectToken> tarObjs; boolean found = false; refOToken = context.getObjectTokenByOutputClassSet(refCSet); if (refOToken == null) {res = true;} // even if we cannot find an object, do not stop execution else { runTraceMessage("get linking association from class " + refOToken.className() + " by association " + assocName + " to class " + tarCSet.className() + " at target end " + targetEnd); tarObjs = new Vector<objectToken>(); try{ tarObjs = oGet.getAssociatedObjects(refOToken,assocName,tarCSet.className(),startEnd); } catch (Exception ex) {System.out.println(ex.getMessage());} runTraceMessage("found: " + tarObjs.size() + " candidates for context"); for (int i = 0; i < tarObjs.size(); i++) { tarOToken = (objectToken)tarObjs.elementAt(i); // only include the first object which passes the inclusion filters of the output XML if (XT.passesFilter(tarOToken,tarCSet.subset(),assocName,context)) { if (!found) { context.addObject(tarOToken,tarCSet); } found = true; } } res = true; // regardless of how many objects we found } return res; } /* */ boolean XSLTForStep() throws MapperException // addToContextStep { return addAssociatedClassVariable(refCSet,tarCSet,assocName, targetEnd); } void writeStep() {message(stepPrefix() + ": traverse " + cSet1.stringForm() + " " + assocName + " " + cSet2.stringForm() + " " + " to end " + targetEnd + " to add an object to the context.");} } // method to add this step to the procedure public void as_addToContext(ClassSet cs1, String aName, ClassSet cs2, int te,String caseNo) throws RunTranslateException { int step = steps.size(); steps.addElement(new addToContextStep(cs1,aName,cs2,te,step, caseNo)); } // support method reused by relatedPropertiesStep /** * @param refCSet output ClassSet of the reference Class, expected to be in the XSL generation context * @param tarCSet output ClassSet of the target class, to be added to the generation context, and a new xsl variable * to be created for the class * @param assocName association name * @param targetEnd end of the association for the target object. * Generate XSL to navigate the association from the reference object to the target object, * putting the target object in the generation context and making two new xsl variables - * one for the association node (not used elsewhere) and one for the target object, named after its output ClassSet */ private boolean addAssociatedClassVariable(ClassSet refCSet, ClassSet tarCSet, String assocName, int targetEnd) throws MapperException { boolean res = true; objectRep refObj = (objectRep)context.getObjectTokenByOutputClassSet(refCSet); if (refObj == null) {} // even if we cannot find an object, do not stop execution else { // find the input association mappings (there should be just one), and the target end object mapping Vector<AssociationMapping> inAssocMaps = inputAssocMappings(refObj.cSet(), assocName, tarCSet.className(), targetEnd); if (inAssocMaps.size() != 1) { String problem =("Found " + inAssocMaps.size() + " input association mappings " + "when traversing from input ClassSet " + refObj.cSet().stringForm() + " to class " + tarCSet.className() + " by association " + assocName); int nature = RunIssue.RUN_CONTEXT; runWarningMessage(nature, problem); } AssociationMapping am = inAssocMaps.elementAt(0); objectMapping om = inputTargetObjectMapping(am,targetEnd); if ((am != null) && (om != null)) { /* make variables for association name and target object name */ String assocVar = assocVariable(refObj.cSet(),am,targetEnd,true); targetObjectVariable(assocVar,am,targetEnd,tarCSet,false,true); // make an objectRep with a null node, to record the input class and subset of the target objectRep oRep = makeObjectRepForContext(om.XSLCSet()); context.addObject(oRep,tarCSet); } } return res; } //--------------------------------------------------------------------------- /* Set the text content of the current element equal to the current string, and reset the current string to ""*/ class setTextStep extends step { boolean appendFlag; // the case (appendFlag = true) is currently not used setTextStep(boolean af, int stno, String cno) { stepNumber = stno; caseNo = cno; stepType = "setText"; stepTypeIndex = Timer.WPROC_STEP + 4; appendFlag = af; } // constructor from XML procedures file setTextStep(Element stepEl) { doSharedProps(stepEl); appendFlag = StringToBoolean(stepEl.getAttribute("appendFlag")); } // XML output to procedures file public Element writeToXML(XMLOutputFile xout) { Element stepEl = super.writeToXML( xout); stepEl.setAttribute("appendFlag",BooleanToString(appendFlag)); return stepEl; } /* Set the text content of the current element equal to the current string. If the text value is already set and append flag=false, check the previous value is equal to the current string. Runtime error if not. If the text value is already set, and append flag=true, append the current string after the old value, separated by a space */ boolean executeStep() throws MapperException { boolean res = true; String previousText = XMLUtil.getText(currentEl); // element contains no text; insert the current string (if it is not empty) if ((previousText.equals("")) && !(currentString.equals(""))) { try{ Text tNew = xout.outDoc().createTextNode(currentString); currentEl.appendChild(tNew); } catch (Exception e) {fatalMessage("Exception creating text-filled element " + e.getMessage());res = false;} } // element contains text; append to it (if the current string is not empty) else if ((appendFlag)&& !(currentString.equals(""))) { String newText = previousText + " " + currentString; try{ Text text = XMLUtil.firstTextChild(currentEl); text.replaceWholeText(newText); } catch (Exception e) {fatalMessage("Exception appending to text-filled element " + e.getMessage());res = false;} } // element contains text; check it is equal to the new value else if (!appendFlag) { if (!currentString.equals(previousText)) {fatalMessage("Error: new text value '" + currentString + "' is not equal to previous value '" + previousText + "'.");res = false;} } currentString = ""; return res; } /* does not attempt to handle the case with append flag = true */ boolean XSLTForStep() throws XMLException // setTextStep { boolean res = true; if ((currentStringVar != null) && !appendFlag) { Element valEl = XSLElement("value-of"); valEl.setAttribute("select","$" + currentStringVar); nodeForXSLT.appendChild(valEl); } // error message suppressed, as it will happen often when a property is not represented in thhe input XML // else if (currentStringVar == null) {stepMessage("No current string variable for content of element '" + XW.getLocName(nodeForXSLT) + "'");} else if (appendFlag) {stepMessage("XSLT code generation cannot yet handle appending text values in elements");} return res; } void writeStep() {message(stepPrefix() + ": set text content of element to '" + currentString + "' with append = " + appendFlag);} } // method to add this step to the procedure, only when appropriate, and with correct appendFlag public void as_sText(String caseNo) { int stepNo = steps.size(); // this step is only appropriate if the previous step set the current text if ((stepNo > 0) && (stepNumber(stepNo-1).isSetTextStep())) { // FIXME: I don't think this criterion is complete. What about create/revisit procedures? // if any previous steps have set the content of this element, appendFlag should be true boolean appendFlag = false; for (int s = 0; s < stepNo; s++) if (stepNumber(s) instanceof setTextStep) appendFlag = true; steps.addElement(new setTextStep(appendFlag,stepNo, caseNo)); } } //--------------------------------------------------------------------------- /* Add an attribute with this name to the current element, and with value equal to the value of the current string, and reset the current string to "" */ class addAttributeStep extends step { String attName; boolean appendFlag; addAttributeStep(String an, boolean af, int stno, String cno) { stepNumber = stno; caseNo = cno; stepType = "addAttribute"; stepTypeIndex = Timer.WPROC_STEP + 5; attName = an; appendFlag = af; } // constructor from XML procedures file addAttributeStep(Element stepEl) { doSharedProps(stepEl); appendFlag = StringToBoolean(stepEl.getAttribute("appendFlag")); attName = stepEl.getAttribute("attName"); } // XML output to procedures file public Element writeToXML(XMLOutputFile xout) { Element stepEl = super.writeToXML( xout); stepEl.setAttribute("appendFlag",BooleanToString(appendFlag)); stepEl.setAttribute("attName",attName); return stepEl; } /* Add an attribute with this name to the current element, and with value equal to the value of the current string. If the current string is empty, do not add an attribute. If the attribute already exists and append flag=false, check the previous value is equal to the current string. Runtime error if not. If the attribute already exists and append flag=true, append the current string after the old value, separated by a space. */ boolean executeStep() throws MDLReadException { String oldValue,newValue; boolean res = true; if (!(currentString.equals(""))) { if (!currentEl.getAttribute(attName).equals("")) // attribute already exists { oldValue = currentEl.getAttribute(attName); if (appendFlag) { // append current string to previous value newValue = oldValue + " " + currentString; currentEl.setAttribute(attName,newValue); } // check current string is equal to previous value else if (!(currentString.equals(oldValue))) { stepMessage("Failed check that current string '" + currentString + "' equals " + "previous value '" + oldValue + "' of attribute '" + attName + "'."); res = false; } } /* No existing attribute; add attribute whose value is the current string */ else { currentEl.setAttribute(attName,currentString); } } currentString = ""; return res; } /* to produce xslt like: <xsl:if test="string($v20)"> <xsl:attribute name="OrderNo"> <xsl:value-of select="$v20"/> </xsl:attribute> </xsl:if> Does not address the case where the attribute already exists, or try to check or append values. */ boolean XSLTForStep() throws XMLException { boolean res = true; if ((currentStringVar != null) && !appendFlag) { Element ifEl = XSLElement("if"); ifEl.setAttribute("test","string($" + currentStringVar + ")"); nodeForXSLT.appendChild(ifEl); Element attEl = XSLElement("attribute"); attEl.setAttribute("name",attName); ifEl.appendChild(attEl); Element valEl = XSLElement("value-of"); valEl.setAttribute("select","$" + currentStringVar); attEl.appendChild(valEl); } else if (currentStringVar != null) {stepMessage("No current string variable for attribute '" + attName + "'");} else if (appendFlag) {stepMessage("XSLT code generation cannot yet handle appending text values in attributes");} return res; } void writeStep() {message(stepPrefix() + ": add attribute '" + attName + "' with value '" + currentString + "'");} } // method to add this step to the procedure, only when appropriate public void as_addAtt(String attName,String caseNo) { int stepNo = steps.size(); // this step is only appropriate if the previous step set the current text if ((stepNo > 0) && (stepNumber(stepNo-1).isSetTextStep())) { // if there are any previous addAttributes to the same attribute, appendFlag is true boolean appendFlag = false; for (int s = 0; s < stepNo; s++) if ((stepNumber(s) instanceof addAttributeStep) && (((addAttributeStep)stepNumber(s)).attName.equals(attName))) appendFlag = true; steps.addElement(new addAttributeStep(attName,appendFlag,stepNo, caseNo)); } } //--------------------------------------------------------------------------- // Set the current string to the value of the property. class getPropertyStep extends step { ClassSet cSet; String pName; multiplicity mult; // can be (0..1) or (1..1) getPropertyStep(ClassSet cs, String pn, multiplicity m, int stno, String cno) { stepNumber = stno; caseNo = cno; stepType = "getProperty"; stepTypeIndex = Timer.WPROC_STEP + 6; cSet = cs; pName = pn; mult = m; } // if the class Set matches an old classSet, change it to a new one public void replaceClassSet(ClassSet oldCSet, ClassSet newCSet) { if (cSet.equals(oldCSet)) cSet = newCSet; } // constructor from XML procedures file getPropertyStep(Element stepEl) { doSharedProps(stepEl); cSet = new ClassSet(XMLUtil.firstNamedChild(stepEl,"ClassSet")); pName = propNameFromXML(stepEl.getAttribute("pName")); mult = new multiplicity(StringToBoolean(stepEl.getAttribute("minIs1")),true); } // XML output to procedures file public Element writeToXML(XMLOutputFile xout) { Element stepEl = super.writeToXML( xout); stepEl.appendChild(cSet.XMLOut("ClassSet",xout)); stepEl.setAttribute("pName",propNameToXML(pName)); stepEl.setAttribute("minIs1",BooleanToString(mult.minIs1())); return stepEl; } /* Set the current string to the value of the property for the context object of given class and (output) subset. If the property has no value, set the current string empty. If property = null, set the current string to the key of the context object of the class and subset. */ boolean executeStep() throws MapperException { String pVal = null; boolean res = false; objectToken oTok = context.getObjectTokenByOutputClassSet(cSet); if (oTok == null) { String problem = "No object of class " + cSet.stringForm() + " in context."; int nature = RunIssue.RUN_OBJECT_IN_CONTEXT; runWarningMessage(nature,problem); runTraceMessage(problem); res = true; // if we cannot get the property, we do not want to stop execution } else { if (pName != null) {pVal = outputPropertyValue(oTok, cSet, pName);} else if (pName == null) {pVal = XT.getKey(oTok);} if (pVal == null) {currentString = "";} else {currentString = pVal;} res = true; } return res; } boolean XSLTForStep() throws MapperException // getPropertyStep { boolean res = true; objectRep oRep = null; /* required object can be anywhere in the context stack, as we will use XSLT variables to pick out the correct start node to get properties of any object */ try{oRep = (objectRep)context.getObjectTokenByOutputClassSet(cSet);} catch (Exception ex) // weird case - can it ever happen? { String problem =( "objectToken is not an objectRep in 'getProperty' step: " + ex.getMessage()); int nature = RunIssue.RUN_OBJECT_TOKEN; runWarningMessage(nature, problem); } if (oRep == null) {runTraceMessage("No object of class '" + cSet.className() + "' in context to get property '" + pName + "'.");} else if (pName == null) { currentStringVar = makeKeyVariable(oRep, cSet); // null if there are not sufficient input mappings res = true; // we do not want a lack of mappings in the input to derail things } else { propertyMapping pm = getInputPropertyMapping(oRep.cSet(),pName); // deal with fixed property values neatly - but the property name is wrong (needs attribute name - not accessible here) if ((pm != null) && (pm.fixed())) { // the next 2 lines may be neat but are wrong // nodeForXSLT.setAttribute(pm.propertyName(), pm.value()); // currentStringVar = null; currentStringVar = makeOutputPropertyVariable(oRep, cSet, pName); // now same as variable case - differences occur in makeInputPropertyVariable } // non-fixed value, possibly with a property conversion else { currentStringVar = makeOutputPropertyVariable(oRep, cSet, pName); // null if there are not sufficient input mappings } res = true; // we do not want a lack of mappings in the input to derail things } return res; // must be true } void writeStep() { String opt = "optional"; if (mult.minIs1()) opt = "non-optional"; String pVal = " property '" + pName + "' "; if (pName == null) {pVal = " key "; opt = "";} message(stepPrefix() + ": get " + opt + pVal + "of an object in class " + cSet.stringForm()); } } // method to add this step to the procedure public boolean as_getProp(ClassSet cs, String pName, multiplicity m,String caseNo) { boolean res = false; int step = steps.size(); res = true; steps.addElement(new getPropertyStep(cs,pName,m,step,caseNo)); return res; } //--------------------------------------------------------------------------- // Set the current string to a constant. class getConstantStep extends step { String constant; getConstantStep(String cVal, int stno, String cno) { stepNumber = stno; caseNo = cno; stepType = "getConstant"; stepTypeIndex = Timer.WPROC_STEP + 7; constant = cVal; } // constructor from XML procedures file getConstantStep(Element stepEl) { doSharedProps(stepEl); constant = stepEl.getAttribute("constant"); } // XML output to procedures file public Element writeToXML(XMLOutputFile xout) { Element stepEl = super.writeToXML( xout); stepEl.setAttribute("constant",constant); return stepEl; } /* Set the current string to a constant. */ boolean executeStep() { boolean res = true; currentString = constant; return res; } boolean XSLTForStep() throws XMLException // getConstantStep { boolean res = true; currentStringVar = XX.newVariable(); Element varEl = XSLElement("variable"); varEl.setAttribute("name",currentStringVar); // note use of extra "'" characters to denote a constant varEl.setAttribute("select","'" + constant + "'"); nodeForXSLT.appendChild(varEl); return res; } void writeStep() {message(stepPrefix() + ": put constant '" + constant + "' in the current string ");} } // method to add this step to the procedure public void as_getConst(String cVal,String caseNo) { int step = steps.size(); steps.addElement(new getConstantStep(cVal,step,caseNo)); } //--------------------------------------------------------------------------- /* Append the values of the property for all objects in the target class and subset related to the reference object by a chain of associations. If propName = null, use the unique identifier of the final object in stead of a property. XSLT generation has not yet been extended to the general case, and only deals with chains of length 1. */ /* A chain of associations is represented by a Vector assocChain. Each element of the Vector is a String array aLink of length 4: aLink[0] = association name aLink[1] = target class name aLink[2] = target class subset aLink[3] = target end, "1" or "2" */ class relatedPropertiesStep extends step { ClassSet refCSet; // ClassSet of the start object of the chain Vector<String[]> assocChain; // elements for each link of the chain are String[4] as described above String propName; // name of the property of the final objects, or null means use object identifier String assocName = null; // retained for the special case chain length = 1 ClassSet tarCSet = null; // retained for the special case chain length = 1 int targetEnd; // 1 or 2, retained for the special case chain length = 1 relatedPropertiesStep(ClassSet rcs, Vector<String[]> bChain, String pName, int stno, String cno) throws MapperException { stepNumber = stno; caseNo = cno; stepType = "relatedProperty"; stepTypeIndex = Timer.WPROC_STEP + 8; refCSet = rcs; assocChain = bChain; propName = pName; setOldVariables(); } // if the class Set matches an old classSet, change it to a new one public void replaceClassSet(ClassSet oldCSet, ClassSet newCSet) { if (refCSet.equals(oldCSet)) refCSet = newCSet; if ((tarCSet != null) && (tarCSet.equals(oldCSet))) tarCSet = newCSet; for (int i = 0; i < assocChain.size(); i++) { String[] link = assocChain.get(i); String[] newLink = link; if ((link[1].equals(oldCSet.className())) && (link[2].equals(oldCSet.subset()))) { newLink[1] = newCSet.className(); newLink[2] = newCSet.subset(); assocChain.setElementAt(newLink, i); } } } /* parameters of final association. tarCset is used to get the property and others are used for XSLT generation in the special case on 1 association*/ private void setOldVariables() throws MapperException { String[] aLink = assocChain.elementAt(assocChain.size() - 1); assocName = aLink[0]; tarCSet = new ClassSet(aLink[1],aLink[2]); targetEnd = new Integer(aLink[3]).intValue(); } // constructor from XML procedures file relatedPropertiesStep(Element stepEl) throws MapperException { doSharedProps(stepEl); refCSet = new ClassSet(XMLUtil.firstNamedChild(stepEl,"refClass")); propName = propNameFromXML(stepEl.getAttribute("propName")); assocChain = new Vector<String[]>(); Vector<Element> assocEls = XMLUtil.namedChildElements(stepEl,"assoc"); for (int i = 0; i < assocEls.size(); i++) { Element assocEl = (Element) assocEls.elementAt(i); String[] aLink = new String[4]; aLink[0] = assocEl.getAttribute("name"); aLink[1] = assocEl.getAttribute("targetClass"); aLink[2] = assocEl.getAttribute("targetSubset"); aLink[3] = assocEl.getAttribute("targetEnd"); assocChain.addElement(aLink); } setOldVariables(); } // XML output to procedures file public Element writeToXML(XMLOutputFile xout) { Element stepEl = super.writeToXML( xout); stepEl.appendChild(refCSet.XMLOut("refClass",xout)); stepEl.setAttribute("propName",propNameToXML(propName)); for (int i = 0; i < assocChain.size();i++) { Element assocEl = xout.newElement("assoc"); String[] aLink = assocChain.elementAt(i); assocEl.setAttribute("name",aLink[0]); assocEl.setAttribute("targetClass",aLink[1]); assocEl.setAttribute("targetSubset",aLink[2]); assocEl.setAttribute("targetEnd",aLink[3]); stepEl.appendChild(assocEl); } return stepEl; } /* Find all objects in the target class and subset related to the reference object by the association, satisfying their inclusion filters. Set the current string to the append of their values for the property, separated by spaces. If property = null, set the current string to the append of their unique keys, separated by spaces. */ boolean executeStep() throws MapperException { String pVal; boolean res = false; objectToken refOToken,tarOToken; String appendString = ""; refOToken = context.getObjectTokenByOutputClassSet(refCSet); if (refOToken == null) { int nature = RunIssue.RUN_OBJECT_IN_CONTEXT; runWarningMessage(nature,"No object of class " + refCSet.stringForm() + " in context."); res = true; // even if we cannot find an object, do not stop execution } else { Vector<objectToken> endObjects = followAssociationChain(refOToken); for (int i = 0; i < endObjects.size(); i++) { tarOToken = endObjects.elementAt(i); pVal = null; if (propName != null) {pVal = outputPropertyValue(tarOToken,tarCSet,propName);} else if (propName == null) {pVal = XT.getKey(tarOToken);} if ((pVal != null) && !(pVal.equals(""))) { if (!(appendString.equals(""))) {appendString = appendString + " ";} appendString = appendString + pVal; } } currentString = appendString; res = true; } return res; } /* follow a chain of associations from one start objectToken to a set of objectTokens at the end of the final association. */ Vector<objectToken> followAssociationChain(objectToken startObject) throws MapperException { Vector<objectToken> startObjects = new Vector<objectToken>(); Vector<objectToken> nextObjects = new Vector<objectToken>(); startObjects.addElement(startObject); Vector<objectToken> currentObjects = startObjects; for (int i = 0; i < assocChain.size(); i++) { String[] aLink = assocChain.elementAt(i); String assocName = aLink[0]; ClassSet tarCSet = new ClassSet(aLink[1],aLink[2]); int targetEnd = new Integer(aLink[3]).intValue(); nextObjects = followAssociation(currentObjects,assocName,tarCSet,targetEnd); currentObjects = objectRep.vCopy(nextObjects); // for next link in the association chain } return nextObjects; } /* Given a Vector of objectTokens for objects at the start of an association, find the Vector of objectTokens for all objects linked to them by the association and satisfying their inclusion conditions. */ Vector<objectToken> followAssociation(Vector<objectToken> startObjects, String assocName, ClassSet tarCSet, int targetEnd) throws MapperException { Vector<objectToken> res = new Vector<objectToken>(); if (startObjects.size() > 0) { String className = startObjects.elementAt(0).className(); runTraceMessage("get association from class " + className + " by association " + assocName + " to class " + tarCSet.className() + " at target end " + targetEnd); int startEnd = 3 - targetEnd; for (int i = 0; i < startObjects.size(); i++) { objectToken startObject = startObjects.elementAt(i); Vector<objectToken> tarObjs = oGet.getAssociatedObjects(startObject,assocName,tarCSet.className(),startEnd); runTraceMessage("found: " + tarObjs.size() + " candidate objects"); for (int j = 0; j < tarObjs.size(); j++) { runTraceMessage("checking inclusion filters for candidate " + (j+1) + " of " + tarObjs.size() + " of class " + tarCSet.className() + " found by association " + assocName); objectToken tarOToken = (objectToken)tarObjs.elementAt(j); // only include objects which pass the inclusion filters of the output XML if (XT.passesFilter(tarOToken,tarCSet.subset(),assocName,context)) {res.addElement(tarOToken);} } } } return res; } /* private void setOldVariables() throws MapperException { String[] aLink = assocChain.elementAt(assocChain.size() - 1); assocName = aLink[0]; tarCSet = new ClassSet(aLink[1],aLink[2]); targetEnd = new Integer(aLink[3]).intValue(); } */ boolean handleManyLinksInXSLT = true; /** * if XSLT cannot yet handle many association links, and there is more than one link, * write a warning and do no more * @throws MapperException */ private void giveWarning() throws MapperException { String assocNames = ""; for (Iterator<String[]> it = assocChain.iterator();it.hasNext();) assocNames = assocNames + it.next()[0] + " "; String problem = ( "XSLT generation does not yet handle more than 1 association finding a link condition property; path " + pathSpec().stringForm() + "; associations " + assocNames); int nature = RunIssue.RUN_XSLT_ASSOCIATION; runWarningMessage(nature,problem); } boolean XSLTForStep() throws MapperException // relatedPropertiesStep { boolean res = true; ClassSet startForCurrentLink = refCSet; // remains true if if (assocChain.size() == 1) trace("Related properties XSL at path " + pathSpec.stringForm()); if (assocChain.size() > 1) { if (!handleManyLinksInXSLT) giveWarning(); // do all association links except the last else for (int link = 0; link < assocChain.size()-1; link++) if (res) { String[] aLink = assocChain.elementAt(link); String currentAssocName = aLink[0]; ClassSet currentTarCSet = new ClassSet(aLink[1],aLink[2]); int currentTargetEnd = new Integer(aLink[3]).intValue(); // create an XSL variable for the next class in the chain, and add it to the context res = res && addAssociatedClassVariable(startForCurrentLink, currentTarCSet, currentAssocName, currentTargetEnd); // get ready to handle the next link (which may be the last) startForCurrentLink = currentTarCSet; } } /* Last link in the chain of associations is treated differently from the rest. * Check that the reference object in context is somewhere in the context stack; otherwise we have no variable on the correct input XML node to start navigating the association XPath. */ objectToken oRef = context.getObjectTokenByOutputClassSet(startForCurrentLink); if ((oRef != null) && res) { // new variable representing current string String vName = XX.newVariable(); currentStringVar = vName; Element relVar = XSLElement("variable"); relVar.setAttribute("name",vName); /* this variable has no 'select' attribute, but its value is got from its 'template body' contents. */ Element keepNodeForXSLT = nodeForXSLT; nodeForXSLT.appendChild(relVar); nodeForXSLT = relVar; // populate the variable by navigating the association res = associationXSL(((objectRep)oRef).cSet(),assocName,tarCSet,targetEnd,null,propName); nodeForXSLT = keepNodeForXSLT; } else { res = false; int nature = RunIssue.RUN_OBJECT_IN_CONTEXT; runWarningMessage(nature,"Error appending values of property '" + propName + "' for associated objects; class " + refCSet.stringForm() + " is not in the context stack."); } return res; } void writeStep() { String pVal = " values of property '" + propName + "' "; if (propName == null) {pVal = " key values ";} message(stepPrefix() + ": append" + pVal + "for objects in class " + tarCSet.stringForm() + " reached from an object in class " + refCSet.stringForm() + " by associations: " ); for (int i = 0 ; i < assocChain.size(); i++) { String[] aLink = assocChain.elementAt(i); message("(" + (i+1) + ") '" + aLink[0] + "' to class '" + aLink[1] + "' subset '" + aLink[2] + "' at end " + aLink[3]); } } } /* method to add this step to the procedure; version retained for compatibility with calls to previous version when the association chain was only allowed to be of length 1. */ public boolean as_relProps(ClassSet refCS, String assocName, ClassSet tarCS, int targetEnd, String pName,String caseNo) throws MapperException{ boolean res = true; int step = steps.size(); // convert arguments to an association chain of length 1 Vector<String[]> aChain = new Vector<String[]>(); String[] aLink = new String[4]; aLink[0] = assocName; aLink[1] = tarCS.className(); aLink[2] = tarCS.subset(); aLink[3] = new Integer(targetEnd).toString(); aChain.addElement(aLink); steps.addElement(new relatedPropertiesStep(refCS,aChain,pName,step,caseNo)); return res; } /* method to add this step to the procedure; New version allows arbitrary length of association chain */ public boolean as_relProps(ClassSet refCS, Vector<String[]> aChain, String pName,String caseNo) throws MapperException{ boolean res = true; int step = steps.size(); steps.addElement(new relatedPropertiesStep(refCS,aChain,pName,step,caseNo)); return res; } //--------------------------------------------------------------------------- // set a when-condition value class setWhenValueStep extends step { whenValue wVal; setWhenValueStep(whenValue wv, int stno, String cno) { wVal = wv; stepNumber = stno; caseNo = cno; stepType = "setWhenValue"; stepTypeIndex = Timer.WPROC_STEP + 9; } // constructor from XML procedures file setWhenValueStep(Element stepEl) throws MapperException { doSharedProps(stepEl); // namespace set in wVal.rootPath is for output document wVal = new whenValue(XMLUtil.firstNamedChild(stepEl,"whenValue"), XW.NSSet()); } // XML output to procedures file public Element writeToXML(XMLOutputFile xout) { Element stepEl = super.writeToXML( xout); stepEl.appendChild(wVal.XMLOut("whenValue",xout)); return stepEl; } /* Set the value of a when-conditions - which will be stored somewhere in the unique subtree of the current node. Record class, subset and property in the subtree context. If they are non-null (i.e. if a when-condition value is also a property) the property value will be used as an extra inclusion filter for objects of the class and subset. All arguments above are contained in a whenValue object. */ boolean executeStep() throws MDLWriteException { boolean success = true; context.setWhenValue(wVal); return success; } boolean XSLTForStep() throws MDLWriteException // setWhenValueStep { boolean res = true; context.setWhenValue(wVal); return res; } void writeStep() {message(stepPrefix() + ": pre-set when-value " + wVal.rootPath().stringForm() + " to '" + wVal.value() + "'" );} } // method to add this step to the procedure public void as_setWVal(whenValue wv,String caseNo) { int step = steps.size(); steps.addElement(new setWhenValueStep(wv,step,caseNo)); } //--------------------------------------------------------------------------- // Get all objects in a class and subset satisfying the inclusion filters for the subset. class allObjectStep extends step { ClassSet cSet; String tagName; boolean specialNode; allObjectStep(ClassSet cs, String tn, boolean spec, int stno, String cno) { stepNumber = stno; caseNo = cno; stepType = "getAllObjects"; stepTypeIndex = Timer.WPROC_STEP + 10; cSet = cs; tagName = tn; specialNode = spec; } // if the class Set matches an old classSet, change it to a new one public void replaceClassSet(ClassSet oldCSet, ClassSet newCSet) { if (cSet.equals(oldCSet)) cSet = newCSet; } // constructor from XML procedures file allObjectStep(Element stepEl)throws MapperException { doSharedProps(stepEl); cSet = new ClassSet(stepEl.getAttribute("class"),stepEl.getAttribute("subset")); tagName = stepEl.getAttribute("tagName"); specialNode = StringToBoolean(stepEl.getAttribute("specialNode")); } // XML output to procedures file public Element writeToXML(XMLOutputFile xout) { Element stepEl = super.writeToXML( xout); stepEl.setAttribute("class",cSet.className()); stepEl.setAttribute("subset",cSet.subset()); stepEl.setAttribute("tagName",tagName); stepEl.setAttribute("specialNode",BooleanToString(specialNode)); return stepEl; } /* Get all objects in a class and subset satisfying the inclusion filters for the subset. if specialNode = true, do not use the normal inclusion filters for the class; use a special set of inclusion filters defined in the steering file. If there are no grouping criteria, for each object create an element with the tag name, add the object to the context, and do the procedure for the new element. If there are some grouping criteria in force, for each object: Add the object to the context, and either find an element which matches the grouping criteria values for the object, or create a new element with those values linked to it. Do the procedure for this element. */ boolean executeStep() throws MapperException { Xpth newPath; boolean success = true; Vector<objectToken> allObjs = oGet.getObjects(cSet.className()); subStep = 1; groupingVectors = new Vector<Vector<String>>(); groupEls = new Vector<Element>(); runTraceMessage("Found " + allObjs.size() + " objects"); for (int i = 0; i < allObjs.size(); i++) if (success) { objectToken oTok = (objectToken)allObjs.elementAt(i); subStep = 2; runTraceMessage("Object " + i + " of class " + cSet.stringForm()); // only include objects satisfying the inclusion filters for the output subset if (rightFilter(oTok,cSet,context)) { subStep = 3; subtreeContext newContext = context.copySC(); subStep = 4; newContext.addObject(oTok,cSet); subStep = 5; newPath = rootPath.addInnerStep(tagName); subStep = 6; success = success && doMaybeGroupedElement(stepTypeIndex,tagName,newPath,newContext); } else runTraceMessage("Failed filters"); } return success; } /* cannot generate XSLT if there are grouping criteria in force. With no groups, we have to iterate over all subclasses and subsets in the input XML. */ boolean XSLTForStep() throws MapperException // for allObjectStep { trace("Input mappings for subclasses of " + cSet.stringForm()); int tried = 0; int succeeded = 0; boolean res = true; // find the output object mapping, for its inclusion filters objectMapping outputMap = XW.namedObjectMapping(cSet); if (outputMap == null) { int nature = RunIssue.RUN_OUTPUT_MAPPING; runWarningMessage(nature,"Missing output object mapping for class " + cSet.stringForm()); } // iterate over all subclasses of the class required Vector<String> subclasses = ModelUtil.allSubclassNames(cSet.className(), XW.ms().getClassModelRoot()); /* check the total number of input mappings to subclasses; * if it is large, there must be some filters */ int nMappings = mappingCount(subclasses); if ((nMappings > 3) && (outputMap.filterAssocs().size() == 0)) throw new MapperException(nMappings + " is too many object mappings to class " + cSet.stringForm() + " and its subclasses, with no inclusion filters "); for (int i = 0; i < subclasses.size(); i++) { // find all mappings to the subclass in all input mapping sets, organised by the full XPaths of the mappings String subclassName = subclasses.elementAt(i); Hashtable<String,Vector<objectMapping>> objMappings = objectMappingFullPaths(subclassName); // find all distinct full XPaths which have mappings to the subclass for(Enumeration<String> en = objMappings.keys(); en.hasMoreElements();) { String fullXPath = en.nextElement(); // find all object mappings to the subclass with this full XPath Vector<objectMapping> oMaps = objMappings.get(fullXPath); for (Iterator<objectMapping> it = oMaps.iterator(); it.hasNext();) { objectMapping om = it.next();subStep = 1; tried++; /* Static test of output association filters; * only use input mappings whose filters are at least as strong as those of the output mapping */ if (hasStrongerAssociationFilters(om, outputMap,0)) { trace("success on path " + fullXPath); succeeded++; /* put the input reader of the object mapping at the top of the input reader stack, if it is not already there; * and note its stack position. */ addReaderToStack(om); // can only have when-conditions, no link conditions, for an object mapping String XPath = XX.convertPathPrefixes(fullXPath) // full root path in input XML + om.XPathWhenTests(XX) // input when-conditions + outputMap.XPathPropertyInclusionFilter(XX,om); //dynamic test of output XML property inclusion filters String objNodeSet= XX.newVariable(); subStep = 2; attachVariableNode(objNodeSet,XPath, false); // apply a template with the nodeset for all objects of the class Element apply = XSLElement("apply-templates"); subStep = 3; apply.setAttribute("select","$" + objNodeSet); subStep = 4; String mode = XX.newMode(om.className()); apply.setAttribute("mode",mode); nodeForXSLT.appendChild(apply); subStep = 5; /* generate the template which is to be applied, and add the necessary <with-param> elements to the <apply-templates> element The first argument null forces the use of the outer input mapping set in the objectRep in context. */ WProc elProc = createElementTemplate(null,cSet,om,mode,tagName); addParametersToCallOrApply(apply,elProc); } } } } trace("Successful paths for " + cSet.stringForm() + ": " + succeeded + " out of " + tried + " tried."); return res; } /** * count the total number of input object mappings to a set of subclasses * @param subclasses * @return */ private int mappingCount(Vector<String> subclasses) { int count = 0; for (int i = 0; i < subclasses.size(); i++) { // find all mappings to the subclass in all input mapping sets, organised by the full XPaths of the mappings String subclassName = subclasses.elementAt(i); Hashtable<String,Vector<objectMapping>> objMappings = objectMappingFullPaths(subclassName); // find all distinct full XPaths which have mappings to the subclass for(Enumeration<String> en = objMappings.keys(); en.hasMoreElements();) { String fullXPath = en.nextElement(); // find all object mappings to the subclass with this full XPath Vector<objectMapping> oMaps = objMappings.get(fullXPath); count = count + oMaps.size(); } } return count; } /* Apply usual inclusion filters or special start filters from the steering file, depending on boolean specialNode */ boolean rightFilter(objectToken oTok, ClassSet cSet, subtreeContext context) throws MapperException { boolean res = true; if (specialNode) // special filters defined in the steering file { res = XT.specialFilter(oTok); } else // usual inclusion filters for the object { res = XT.passesFilter(oTok,cSet.subset(),null,context); } return res; } void writeStep() { message(stepPrefix() + ": add elements '" + tagName + "' for objects in class " + cSet.stringForm()); if (specialNode) {message("(Taking inclusion filters only from the steering file)");} } } // method to add this step to the procedure public boolean as_allObj(ClassSet cs, String tagName, boolean specNode,String caseNo) throws MDLWriteException { boolean res = false; int step = steps.size(); res = true; steps.addElement(new allObjectStep(cs,tagName,specNode,step,caseNo)); return res; } //--------------------------------------------------------------------------- /* Find all pairs of objects in the two classes linked by the association and satisfying their inclusion filters. */ class associationInstanceStep extends step { ClassSet cSet1; String assocName; ClassSet cSet2; /* which end objects are expected at code generation to be not in the runtime context: 0 - neither; 1,2 - that end; 3 - both */ int endsNotInContext; multiplicity mult; String tagName; associationInstanceStep(ClassSet cs1, String an, ClassSet cs2, int endsNotIn, multiplicity m, String tn, int stno, String cno) throws MapperException { stepNumber = stno; caseNo = cno; stepType = "associationInstance"; stepTypeIndex = Timer.WPROC_STEP + 11; cSet1 = cs1; assocName = an; cSet2 = cs2; endsNotInContext = endsNotIn; mult = m; tagName = tn; if ((cSet1.equals(cSet2)) && !((endsNotInContext ==1)|(endsNotInContext ==2))) {fatalMessage("Parameter endsNotInContext of step assInst should be 1 or 2 for self-association '" + assocName + "'");} } // if the class Set matches an old classSet, change it to a new one public void replaceClassSet(ClassSet oldCSet, ClassSet newCSet) { if (cSet1.equals(oldCSet)) cSet1 = newCSet; if (cSet2.equals(oldCSet)) cSet2 = newCSet; } // constructor from XML procedures file associationInstanceStep(Element stepEl) { doSharedProps(stepEl); cSet1 = new ClassSet(XMLUtil.firstNamedChild(stepEl,"classSet1")); cSet2 = new ClassSet(XMLUtil.firstNamedChild(stepEl,"classSet2")); assocName = stepEl.getAttribute("assocName"); tagName = stepEl.getAttribute("tagName"); endsNotInContext = StringToInt(stepEl.getAttribute("endsNotInContext")); boolean minIs1 = StringToBoolean(stepEl.getAttribute("minIs1")); boolean maxIs1 = StringToBoolean(stepEl.getAttribute("maxIs1")); mult = new multiplicity(minIs1,maxIs1); } // XML output to procedures file public Element writeToXML(XMLOutputFile xout) { Element stepEl = super.writeToXML( xout); stepEl.appendChild(cSet1.XMLOut("classSet1",xout)); stepEl.appendChild(cSet2.XMLOut("classSet2",xout)); stepEl.setAttribute("assocName",assocName); stepEl.setAttribute("tagName",tagName); stepEl.setAttribute("endsNotInContext",IntToString(endsNotInContext)); stepEl.setAttribute("minIs1",BooleanToString(mult.minIs1())); stepEl.setAttribute("maxIs1",BooleanToString(mult.maxIs1())); return stepEl; } /* Find all pairs of objects in the two classes linked by the association and satisfying their inclusion filters, and compatible with the objects currently in the subtree context. There are four distinct sub-cases: (1) Neither end object is in the subtree context; get all association instances (2) One end object is in the subtree context; get all objects associated to it (3) Both end objects are in the subtree context; (4) Self-association For cases (1) and (2): If there are no grouping criteria in force, for each pair, add one or both both objects to the context, create a child element with the tag name and do the procedure for the child element. If there are some grouping criteria in force, for each pair of objects: Add one or both objects to the context, and either find an element which matches the grouping criteria values for the objects, or create a new element with those values linked to it. Do the procedure for this element. For case (3) : add an element if they have the association. */ boolean executeStep() throws MapperException { Xpth newPath; objectToken end1Token, end2Token; boolean success = true; // in case there are any grouping criteria in force ... groupingVectors = new Vector<Vector<String>>(); groupEls = new Vector<Element>(); /* find if either of both end objects is already in the subtree context, and check this against the expectation from code generation. */ end1Token = context.getObjectTokenByOutputClassSet(cSet1); end2Token = context.getObjectTokenByOutputClassSet(cSet2); boolean expectedEndsFound = true; if ((end1Token == null) && ((endsNotInContext == 0)|(endsNotInContext == 2))) { expectedEndsFound = false; /* 3/10/09 not finding an expected object in context is not a fatal error, or even a warning; * it can just happen if the source does not represent it, so return success = true. The step then does not make any elements, as objects cannot be found through the association */ // fatalMessage("Did not find expected object of class " + cSet1.stringForm() + " in context."); return true; } if ((end2Token == null) && ((endsNotInContext == 0)|(endsNotInContext == 1))) { /* 3/10/09 not finding an expected object in context is not a fatal error, or even a warning; * it can just happen if the source does not represent it, so return success = true. The step then does not make any elements, as objects cannot be found through the association */ expectedEndsFound = false; // fatalMessage("Did not find expected object of class " + cSet2.stringForm() + " in context."); return true; } /* Case 1: neither end object is in the subtree context. Find all possible candidates for the object at end 1 of the association */ if ((end1Token == null) && (end2Token == null) && expectedEndsFound) { runTraceMessage("Neither end object in context"); Vector<objectToken> allObjs1 = oGet.getObjects(cSet1.className()); runTraceMessage("Found " + allObjs1.size() + " objects of class " + cSet1.className()); for (int i = 0; i < allObjs1.size(); i++) if (success) { objectToken oTok1 = (objectToken)allObjs1.elementAt(i); // only include objects at end 1 satisfying the inclusion filters for the output subset if (XT.passesFilter(oTok1,cSet1.subset(),null,context)) { subtreeContext newContext1 = context.copySC(); newContext1.addObject(oTok1,cSet1); success = findAssociatedObjects(newContext1,oTok1,cSet2,2); } } // end of loop over objects at end 1 } // end of case when neither end object is in the subtree context /* case 2: one of the two end objects is already in the subtree context */ else if ((end1Token != null) && (end2Token == null) && expectedEndsFound) { runTraceMessage("Object at end 1 in context"); success = findAssociatedObjects(context, end1Token,cSet2,2); } else if ((end1Token == null) && (end2Token != null) && expectedEndsFound) { runTraceMessage("Object at end 2 in context"); success = findAssociatedObjects(context, end2Token,cSet1,1); } /* case 3: both end objects are already in the subtree context and it is not a self-association*/ else if ((end1Token != null) && (end2Token != null) && (!cSet1.equals(cSet2))) { runTraceMessage("Both end objects in context, different classes"); // find all objects associated to the end 1 object in the context Vector<objectToken> assObjs = oGet.getAssociatedObjects(end1Token,assocName,cSet2.className(),1); for (int i = 0; i < assObjs.size(); i++) { objectToken oTok = (objectToken)assObjs.elementAt(i); // if one of the associated objects is the end 2 object in the context if (XT.getKey(oTok).equals(XT.getKey(end2Token))) { newPath = rootPath.addInnerStep(tagName); Element el = newElement(xout,tagName); addNewElement(el); context.setRootPath(newPath); // call a procedure for the new created element success = XT.callProcedure(stepTypeIndex,el,true,newPath,context,runIssues); } } } /* case 4: self-association, one end in the subtree context */ else if ((end1Token != null) && (cSet1.equals(cSet2))) { runTraceMessage("Self-association, one end object in context"); success = findAssociatedObjects(context, end1Token, cSet1, endsNotInContext); } return success; } boolean findAssociatedObjects(subtreeContext rCon, objectToken refToken, ClassSet tarCSet, int targetEnd) throws MapperException { Xpth newPath; runTraceMessage("Find associated objects:"); boolean success =true; // find all objects associated to the reference object. int startEnd = 3-targetEnd; Vector<objectToken> assObjs = oGet.getAssociatedObjects(refToken,assocName,tarCSet.className(),startEnd); runTraceMessage("Found " + assObjs.size() + " objects of class " + tarCSet.className()); for (int i = 0; i < assObjs.size(); i++) if (success) { runTraceMessage("Checking filters for object " + (i+1) + " of " + assObjs.size()); objectToken oTok = assObjs.elementAt(i); /* only include objects satisfying the inclusion filters for the output subset; do not re-check any filter on the association used to get the objects. */ if (XT.passesFilter(oTok,tarCSet.subset(),assocName,context)) { subtreeContext newContext = rCon.copySC(); newContext.addObject(oTok,tarCSet); newPath = rootPath.addInnerStep(tagName); success = success && doMaybeGroupedElement(stepTypeIndex,tagName,newPath,newContext); } } return success; } boolean XSLTForStep() throws MapperException // for associationInstanceStep { objectRep end1Rep= null, end2Rep = null; boolean success = true; // find if either of both end objects is already in the subtree context try{end1Rep = (objectRep)context.getObjectTokenByOutputClassSet(cSet1); end2Rep = (objectRep)context.getObjectTokenByOutputClassSet(cSet2);} catch (Exception ex) { String problem =( "objectToken is not an objectRep in 'associated objects' step: " + ex.getMessage()); int nature = RunIssue.RUN_OBJECT_TOKEN; runWarningMessage(nature, problem); } /* Case 1: neither end object is in the subtree context. */ if ((end1Rep == null) && (end2Rep == null)) { int nature = RunIssue.RUN_XSLT_ASSOCIATION; runWarningMessage(nature,"XSLT Generation does not yet handle associations with neither end object in context"); } /* case 2: one of the two end objects is already in the subtree context */ else if ((end1Rep != null) && (end2Rep == null)) { runTraceMessage("Object at end 1 in context"); success = associationXSL(end1Rep.cSet(), assocName, cSet2,2,tagName,null); } else if ((end1Rep == null) && (end2Rep != null)) { runTraceMessage("Object at end 2 in context"); success = associationXSL(end2Rep.cSet(), assocName, cSet1,1,tagName,null); } /* case 3: both end objects are already in the subtree context and it is not a self-association*/ else if ((end1Rep != null) && (end2Rep != null) && (!cSet1.equals(cSet2))) { runTraceMessage("Both end objects in context"); /* choose the start end to navigate the association, so that the variable name for the class at the target end does not clash with any existing variable declared in the template. Do not test (as the Java interpreter does) that any object found at the target end is the same object as the one of that ClassSet already in context. */ if (!xslout.hasClassVar(cSet2.className(),templateNode)) // add variable for CSet2 { success = associationXSL(end1Rep.cSet(), assocName, cSet2, 2, tagName, null); } else if (!xslout.hasClassVar(cSet1.className(),templateNode)) // add variable for CSet1 { success = associationXSL(end2Rep.cSet(), assocName, cSet1, 1, tagName, null); } else { runTraceMessage("For association '" + assocName + "' both end objects are already in context " + " and have variables named after their classes '" + cSet1.className() + "' and '" + cSet2.className() + "'"); } } /* case 4: self-association, one end in the subtree context */ else if ((end1Rep != null) && (cSet1.equals(cSet2))) { runTraceMessage("Self-association, one end object in context"); success = associationXSL(end1Rep.cSet(), assocName, cSet1, endsNotInContext,tagName,null); } return success; } void writeStep() { message(stepPrefix() + ": add elements '" + tagName + "' for instances of association [" + cSet1.stringForm() + "]" + assocName + "[" + cSet2.stringForm() + "]"); } } /* method to add this step to the procedure. endsNotInCon denotes which ends of the association are not expected to be already in context: 0 - neither end; 1,2 - that end; 3 - both ends. */ public boolean as_assInst(ClassSet cs1, String assocName, ClassSet cs2, int endsNotInCon, multiplicity m, String tagName,String caseNo) throws MapperException { boolean res = false; int step = steps.size(); /* sometimes this step will need to get all objects of class 1, when neither end object is in context. In those cases, OXWriter has already checked there is a factory method. */ res = true; steps.addElement(new associationInstanceStep(cs1,assocName, cs2, endsNotInCon,m , tagName,step,caseNo)); return res; } //--------------------------------------------------------------------------- /* Prime the procedure to group nested elements by the value of the property for a context object. */ class groupingPropertyStep extends step { ClassSet cSet; String pName; groupingPropertyStep(ClassSet cs, String pn, int stno, String cno) { stepNumber = stno; caseNo = cno; stepType = "groupingProperty"; stepTypeIndex = Timer.WPROC_STEP + 12; cSet = cs; pName = pn; } // if the class Set matches an old classSet, change it to a new one public void replaceClassSet(ClassSet oldCSet, ClassSet newCSet) { if (cSet.equals(oldCSet)) cSet = newCSet; } // constructor from XML procedures file groupingPropertyStep(Element stepEl)throws MapperException { doSharedProps(stepEl); cSet = new ClassSet(stepEl.getAttribute("class"),stepEl.getAttribute("subset")); pName = propNameFromXML(stepEl.getAttribute("pName")); } // XML output to procedures file public Element writeToXML(XMLOutputFile xout) { Element stepEl = super.writeToXML( xout); stepEl.setAttribute("class",cSet.className()); stepEl.setAttribute("subset",cSet.subset()); stepEl.setAttribute("pName",propNameToXML(pName)); return stepEl; } /* Prime the procedure to group nested elements by the value of the property for a context object. */ boolean executeStep() { groupingCriteria.addElement(new groupingCriterion(cSet,pName)); grouping = true; return true; } boolean XSLTForStep() throws MDLWriteException // groupingPropertyStep { boolean res = false; int nature = RunIssue.RUN_XSLT_GROUPING; runWarningMessage(nature,"XSLT code generation cannot yet handle grouping nodes."); return res; } void writeStep() { String pVal = " property '" + pName + "' "; if (pName == null) {pVal = " key ";} message(stepPrefix() + ": prepare to group child elements by" + pVal + "of objects of class " + cSet.stringForm() ); } } // method to add this step to the procedure public boolean as_grProp(ClassSet cs, String pName,String caseNo) { boolean res = false; int step = steps.size(); res = true; steps.addElement(new groupingPropertyStep(cs,pName,step,caseNo)); return res; } //--------------------------------------------------------------------------- // Undo any previous priming of the procedure to group objects class unPrimeStep extends step { unPrimeStep(int stno, String cno) { stepNumber = stno; caseNo = cno; stepType = "unPrime"; stepTypeIndex = Timer.WPROC_STEP + 13; } // constructor from XML procedures file unPrimeStep(Element stepEl) { doSharedProps(stepEl); } // XML output to procedures file public Element writeToXML(XMLOutputFile xout) { Element stepEl = super.writeToXML( xout); return stepEl; } /* Undo any previous priming of the procedure to group objects; i.e undo the effect of any previous grouping steps. */ boolean executeStep() { groupingCriteria = new Vector<groupingCriterion>(); grouping = false; return true; } boolean XSLTForStep() throws MDLWriteException //unPrimeStep { boolean res = false; int nature = RunIssue.RUN_XSLT_GROUPING; runWarningMessage(nature,"XSLT code generation cannot yet handle grouping nodes."); return res; } void writeStep() {message(stepPrefix() + ": impose no grouping of subsequent child elements.");} } // method to add this step to the procedure public void as_unPrime(String caseNo) { int step = steps.size(); steps.addElement(new unPrimeStep(step,caseNo)); } //------------------------------------------------------------------------------ // Applying grouping criteria //------------------------------------------------------------------------------ /* Declarations at the top: boolean grouping = false; //whether there are any grouping criteria in force Vector groupingCriteria = new Vector(); // Vector of groupingCriterion objects Vector groupingVectors; // runtime values of groups for the grouping criteria Vector groupEls; // elements put in output XML for groups */ class groupingCriterion { public ClassSet cSet; public String propName; // if null, the 'property' is the object key groupingCriterion(ClassSet cs, String pn) { cSet = cs; propName = pn; } } /* many different steps have to add a new element. Usually this is just appended to the current element - but if that is null, the new element needs to be the root element of the document. */ void addNewElement(Element el) throws MDLWriteException { if (currentEl != null) { if (el == null) { String problem =("Null added element"); int nature = RunIssue.RUN_NULL_ELEMENT; runWarningMessage(nature, problem); } currentEl.appendChild(el); } /* if there is no current element, make the element the root of the document. There should not be a root element already. */ else if (xout.topOut() == null) { if (el == null) { String problem =("Null root element"); int nature = RunIssue.RUN_NULL_ELEMENT; runWarningMessage(nature, problem); } xout.setTopOut(el); // declare all output namespaces in the top element xout.addNamespaceAttributes(); } else if (xout.topOut() != null) { String oldTopTag = xout.getLocName(xout.topOut()); String newTopTag = xout.getLocName(el); { String problem =("Document already has a top element named '" + oldTopTag + "'; attempt add another with name '" + newTopTag + "' failed"); int nature = RunIssue.RUN_DUPLICATE_ELEMENT; runWarningMessage(nature, problem); } } } /* process an element which may be grouped or not, given a subtree context and root path. If there is no grouping, just create a new element and do the procedure for it. If there is grouping, check whether the subtree context matches one of the existing groups, according to the grouping criteria in force. If so, go to the existing group element and do the procedure for it. If not, create a new element and do the procedure for it. */ boolean doMaybeGroupedElement(int stepTypeIndex,String tagName, Xpth newPath, subtreeContext newContext) throws MapperException { String mString = null; boolean success = true; runTraceMessage("doMaybeGroupedElement"); newContext.setRootPath(newPath);// may not be necessary; but does no harm // simpler case - no grouping criteria in force if (!grouping) { runTraceMessage("No grouping"); Element el = newElement(xout,tagName); addNewElement(el); success = XT.callProcedure(stepTypeIndex,el,true,newPath,newContext,runIssues); } else // there are some grouping criteria in force { runTraceMessage("Grouping by " + groupingCriteria.size() + " criteria"); if (groupEls.size() == groupingVectors.size()) // should always be so { boolean matched = false; Element groupEl = null; // test if the subtree context matches any grouping elements added so far for (int j = 0; j < groupingVectors.size(); j++) if (!matched) { Vector<String> gVector = groupingVectors.elementAt(j); matched = groupMatch(newContext, gVector, groupingCriteria); if (matched) { groupEl = groupEls.elementAt(j); mString = "matched"; } } // no grouping elements match the subtree context; add a new grouping element if (!matched) { mString = "not matched"; groupEl = newElement(xout,tagName); groupEls.addElement(groupEl); groupingVectors.addElement(groupVector(newContext,groupingCriteria)); addNewElement(groupEl); } // call a procedure for the created or re-found element runTraceMessage("Calling procedure " + newPath.stringForm() + " :" + mString); success = XT.callProcedure(stepTypeIndex,groupEl,(!matched),newPath,newContext,runIssues); } else { int nature = RunIssue.RUN_GROUP_NUMBERS; runWarningMessage(nature,"Discrepant numbers of groups: " + groupEls.size() + " and " + groupingVectors.size()); success = false; } } return success; } /* true if the values of object keys or properties in the context newContext match the values in the grouping vector gVector according to the criteria in the vector grouping criteria. */ boolean groupMatch(subtreeContext newContext,Vector<String> gVector,Vector<groupingCriterion> groupingCriteria) throws MapperException { String gValue, actValue; boolean match = true; if (gVector.size() == groupingCriteria.size())// should always be true { for (int i = 0; i < gVector.size(); i++) if (match) { gValue = (String)gVector.elementAt(i); groupingCriterion gc = groupingCriteria.elementAt(i); objectToken oTok = newContext.getObjectTokenByOutputClassSet(gc.cSet); if (oTok != null) // should always be true { actValue = ""; if (gc.propName != null) {actValue = oGet.getPropertyValue(oTok,gc.propName);} else {actValue = XT.getKey(oTok);} match = match & (gValue.equals(actValue)); } else { int nature = RunIssue.RUN_OBJECT_IN_CONTEXT; runWarningMessage(nature,"Cannot find object of class " + gc.cSet.stringForm() + " in subtree context for group matching."); match = false; } } } else { int nature = RunIssue.RUN_GROUP_NUMBERS; runWarningMessage(nature,"Mismatched number of grouping criteria: " + gVector.size() + " and " + groupingCriteria.size()); match = false; } return match; } /* create a new grouping vector, describing the values a subtree context has for comparison with grouping criteria. This vector will be associated with a grouping element.*/ Vector<String> groupVector(subtreeContext newContext,Vector<groupingCriterion> groupingCriteria) throws MapperException { String actValue; objectToken oTok; groupingCriterion gc; Vector<String> gv = new Vector<String>(); for (int i = 0; i < groupingCriteria.size(); i++) { gc = groupingCriteria.elementAt(i); oTok = newContext.getObjectTokenByOutputClassSet(gc.cSet); if (oTok != null) // should always be true { actValue = ""; if (gc.propName != null) {actValue = oGet.getPropertyValue(oTok,gc.propName);} else {actValue = XT.getKey(oTok);} gv.addElement(actValue); } else { int nature = RunIssue.RUN_OBJECT_IN_CONTEXT; runWarningMessage(nature,"Cannot find object of class " + gc.cSet.stringForm() + " in subtree context when making a group matching vector."); } } return gv; } //-------------------------------------------------------------------------- // Writing out an XML form of the generation procedures //-------------------------------------------------------------------------- public void writeToXML(Element procsEl, XMLOutputFile xout) { boolean writeContextSets = true; Element procEl = xout.newElement("proc"); // write procedure type String type = "revisit"; if (onCreate) {type = "create"; } procEl.setAttribute("type",type); // write classSets expected in context, without duplicates Element contextEl = xout.newElement("context"); if (writeContextSets) procEl.appendChild(contextEl); Hashtable<String, ClassSet> uniqueContextCsets = new Hashtable<String, ClassSet>(); for (int i = 0; i < expectedContextCSets.size(); i++) { ClassSet ecs = expectedContextCSets.elementAt(i); if (uniqueContextCsets.get(ecs.stringForm()) == null) {contextEl.appendChild(ecs.XMLOut("ClassSet",xout));} uniqueContextCsets.put(ecs.stringForm(),ecs); } // write when-values Element whenEl = xout.newElement("whenConditions"); for (int i = 0; i < whenValues.size(); i++) { whenValue wv = whenValues.elementAt(i); whenEl.appendChild(wv.XMLOut("when",xout)); } if (whenValues.size() > 0) procEl.appendChild(whenEl); // write steps for (int stepNo = 0; stepNo < steps.size(); stepNo++) { step st = steps.elementAt(stepNo); Element stepEl = st.writeToXML(xout); procEl.appendChild(stepEl); } procsEl.appendChild(procEl); } //-------------------------------------------------------------------------- // Reading in an XML form of the generation procedures //-------------------------------------------------------------------------- private step makeStep(Element stepEl) throws MapperException { String stepType = stepEl.getAttribute("stepType"); if (stepType.equals("addElement")) {return new addElementStep(stepEl);} else if (stepType.equals("fillElement")) {return new fillElementStep(stepEl);} else if (stepType.equals("addAttribute")) {return new addAttributeStep(stepEl);} else if (stepType.equals("addToContext")) {return new addToContextStep(stepEl);} else if (stepType.equals("getAllObjects")) {return new allObjectStep(stepEl);} else if (stepType.equals("associationInstance")) {return new associationInstanceStep(stepEl);} else if (stepType.equals("getConstant")) {return new getConstantStep(stepEl);} else if (stepType.equals("getProperty")) {return new getPropertyStep(stepEl);} else if (stepType.equals("groupingProperty")) {return new groupingPropertyStep(stepEl);} else if (stepType.equals("relatedProperty")) {return new relatedPropertiesStep(stepEl);} else if (stepType.equals("setText")) {return new setTextStep(stepEl);} else if (stepType.equals("setWhenValue")) {return new setWhenValueStep(stepEl);} else if (stepType.equals("unPrime")) {return new unPrimeStep(stepEl);} else { fatalMessage("Cannot yet make a step of type '" + stepType + "'"); step st = null; return st; } } private String IntToString(int i) {return new Integer(i).toString();} private int StringToInt(String s) {return new Integer(s).intValue();} private String BooleanToString(boolean b) {if (b) {return "true";} else {return "false";}} private boolean StringToBoolean(String s) {if (s.equals("true")) {return true;} else {return false;}} protected void trace(String s) {if (tracing) System.out.println(s);} //------------------------------------------------------------------------------------------- // stub methods added because the subclasses of inner class step need them // (class WProcXSLT provides real versions of these methods, which override) // The methods here will all fail unless overridden //------------------------------------------------------------------------------------------- protected void addParametersToCallOrApply(Element callOrApply, WProc calledTemplate) throws XMLException {fail();} protected boolean associationXSL(ClassSet endCSet, String assocName, ClassSet cSet, int targetEnd, String tagName, String propName) throws MapperException {fail();return false;} protected void attachVariableNode(String varName, String selectExpression, boolean attachToTemplate) throws XMLException {fail();} public void checkContext(int i) throws RunTranslateException {fail();} protected WProc createElementTemplate(ClassSet inPreviousCSet,ClassSet cSet,objectMapping om,String mode,String tagName) throws MapperException {fail();return null;} protected Element XSLElement(String name) throws XMLException {fail();return null;} protected Vector<AssociationMapping> inputAssocMappings(ClassSet endCSet, String assocName, String targetClassName, int targetEnd) throws MapperException {fail();return null;} protected objectMapping inputTargetObjectMapping(AssociationMapping am, int targetEnd) throws MapperException {fail();return null;} protected void makeAssocInclusionFilters(ClassSet inputCSet, Vector<filterAssoc> assocFilters, String unfiltered, String filtered, boolean attachToTemplate, AssociationMapping am) throws MapperException {fail();} protected String makeKeyVariable(objectRep oRep, ClassSet cSet) throws MapperException {fail();return null;} protected String makeOutputPropertyVariable(objectRep oRep, ClassSet cSet, String pName) throws MapperException {fail();return null;} protected MDLXOReader inputReader(ClassSet inCSet) throws MapperException {fail();return null;} protected objectRep makeObjectRepForContext(ClassSet XSLCSet) throws MapperException {fail();return null;} protected propertyMapping getInputPropertyMapping(ClassSet cSet, String pName) throws MapperException {fail();return null;} protected String targetObjectVariable(String assocVar, AssociationMapping am, int targetEnd, ClassSet targetCSet, boolean firstOnly, boolean attachToTemplate) throws MapperException {fail();return null;} protected String assocVariable(ClassSet inputCSet, AssociationMapping am,int targetEnd, boolean attachToTemplate) throws MapperException {fail();return null;} protected Hashtable<String,Vector<objectMapping>> objectMappingFullPaths(String className) {fail();return null;} protected boolean hasStrongerAssociationFilters(objectMapping inputMap, objectMapping outputMap, int depth) throws MapperException {fail(); return false;} // put the input reader of the object mapping at the top of the input reader stack, if it is not already there protected void addReaderToStack( objectMapping om) throws MapperException {fail();} /** * Print a stack trace if any method is not overridden, which should be */ @SuppressWarnings("null") private void fail() { System.out.println("Failed to override method in WProc by method in WProcXSLT"); try { String failure = null; failure.substring(1); } catch (Exception e) {e.printStackTrace();} } }