package com.openMap1.mapper.reader;
/**
* class to fill out template Word documents saved as XML (and other XML structures)
* from an Ecore model instance before editing,
* and conversely after editing to populate an Ecore model instance
* from the edited Word XML
*/
import java.util.Hashtable;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import com.openMap1.mapper.MappedStructure;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.util.ModelUtil;
import com.openMap1.mapper.util.SystemMessageChannel;
import com.openMap1.mapper.util.XMLUtil;
import com.openMap1.mapper.writer.EMFObjectGetter;
import com.openMap1.mapper.writer.XMLWriter;
import com.openMap1.mapper.writer.objectGetter;
public class TemplateFiller {
// template Word document, annotated with symbols beginning with a special character String
private Element templateRoot;
// distinctive character string at the start of any symbol
private String startString;
// lookup table from symbols to paths in the Ecore model
private Hashtable<String,String> lookupTable;
// Ecore class model
private EPackage classModel;
// entry class of class model (which is an RMIM-like tree of containment associations)
private EClass entryClass;
// ECore EObject with pre-edit information
private EObject preEditObject;
// input mapping set, to create pre-edit Ecore model from some XML
private MappedStructure inputMappingSet;
// true when the XML is MS Word which needs special treatment
private boolean isMSWord;
// path at which to put any non-XML body
private String nonXMLBodyPath = "ClinicalDocument/component/nonXMLBody/text";
static String NO_SYMBOL = "£$No symbol";
static String SYMBOL_PATH_NOT_IN_INSTANCE = "£$symbol path not found";
static String EDIT_PROMPT = "-";
//-------------------------------------------------------------------------------------------------------
// Constructor and initial checks
//-------------------------------------------------------------------------------------------------------
/**
*
* @param templateRoot
* @param lookup
* @param inputMappingSet
* @param startString
* @param isMSWord
* @throws MapperException
*/
public TemplateFiller(Element templateRoot,
Vector<String[]> lookup,
MappedStructure inputMappingSet,
String startString,
boolean isMSWord) throws MapperException
{
this.templateRoot = templateRoot;
this.startString = startString;
this.inputMappingSet = inputMappingSet;
this.isMSWord = isMSWord;
preEditObject = null;
classModel = inputMappingSet.getClassModelRoot();
// find the entry class of the class model
findEntryClass();
// read the lookup csv into a table, and check all paths in the table exist in the class model
makeLookupTable(lookup);
// check that every symbol in the template Word XML is in the lookup table
checkSymbol(this.templateRoot);
}
/**
*
* @param templateRoot
* @param lookup
* @param classModel
* @param startString
* @throws MapperException
*/
public TemplateFiller(Element templateRoot,Vector<String[]> lookup, EPackage classModel, String startString) throws MapperException
{
this.templateRoot = templateRoot;
this.startString = startString;
this.classModel = classModel;
preEditObject = null;
inputMappingSet = null;
// find the entry class of the class model
findEntryClass();
// read the lookup csv into a table, and check all paths in the table exist in the class model
makeLookupTable(lookup);
// check that every symbol in the Word XML is in the lookup table
checkSymbol(this.templateRoot);
}
/**
* find the entry class of the class model
* @throws MapperException
*/
private void findEntryClass() throws MapperException
{
entryClass = null;
for (Iterator<EClass> it = ModelUtil.getAllClasses(classModel).iterator();it.hasNext();)
{
EClass next = it.next();
String entry = ModelUtil.getMIFAnnotation(next, "entry");
if (entry != null) entryClass = next;
}
if (entryClass == null) throw new MapperException("Class model has no entry class");
}
/**
*
* @param lookup
* @throws MapperException
*/
private void makeLookupTable(Vector<String[]> lookup) throws MapperException
{
lookupTable = new Hashtable<String,String>();
for (int i = 1; i < lookup.size(); i++)
{
String[] row = lookup.get(i);
if (row.length != 2) throw new MapperException("Lookup table row length should not be " + row.length);
if (!(isSyntacticSymbol(row[0])))
throw new MapperException("Symbol '" + row[0] + "' should start with '" + startString + "'");
checkValidPath(row[1]);
lookupTable.put(row[0], row[1]);
}
}
/**
* check that a path is a valid path in the class model.
* Does not yet check conditions on steps.
* @param path
*/
private void checkValidPath(String path) throws MapperException
{
String[] steps = pathSteps(path);
// writeSteps(steps, path);
int stepNo = 0;
EClass currentClass = entryClass;
int nSteps = steps.length;
for (int ist = 0; ist < nSteps; ist++)
{
String step = steps[ist];
if (stepNo == 0)
{
if (!(step.equals(entryClass.getName())))
throw new MapperException("Path '" + path + "' does not start with the root class name '" + entryClass.getName() + "'");
}
else if (stepNo > 0)
{
StringTokenizer bits = new StringTokenizer(step,"[]");
String stem = bits.nextToken();
EStructuralFeature feat = currentClass.getEStructuralFeature(stem);
if (feat == null) throw new MapperException("Path '" + path + "' does not match the class model at step '" + stem + "'");
if (feat instanceof EReference)
{
if (stepNo == nSteps - 1) throw new MapperException("Path '" + path + "' cannot end in an EReference " + stem);
EReference ref = (EReference) feat;
currentClass = (EClass)ref.getEType();
}
else if (feat instanceof EAttribute)
{
if (stepNo < nSteps - 1)
throw new MapperException("Attribute '" + stem + "' is before the end of path '" + path + "'");
}
}
stepNo++;
}
}
/**
* recursive check that all symbols (which satisfy the syntactic criteria for a symbol) are in the lookup table
* @param el
* @throws MapperException if any symbol is not in the lookup table
*/
private void checkSymbol(Element el) throws MapperException
{
String content = removeSurroundingSpaces(getTextOnNode(el));
if ((isSyntacticSymbol(content)) && (lookupTable.get(content) == null))
throw new MapperException("Word XML contains symbol '" + content + "' which is not in the lookup table");
for (Iterator<Element> it = XMLUtil.childElements(el).iterator();it.hasNext();)
checkSymbol(it.next());
}
/**
*
* @param content
* @return true if content satisfies the syntactic criteria for a symbol:
* - starts with the start string (e.g. '$')
* - has some following characters
*/
private boolean isSyntacticSymbol(String content)
{
return ((content != null) && (content.startsWith(startString)) && (content.length() > startString.length()));
}
/**
* remove preceding and succeeding spaces from a single-word string,
* in case symbols have had spaces added inadvertently
* @param sym
* @return
*/
private String removeSurroundingSpaces(String sym)
{
String s = "";
StringTokenizer st = new StringTokenizer(sym," ");
if (st.countTokens() == 1) s = st.nextToken();
return s;
}
/**
*
* @param el
* @return the text under a node, taking account of how MS Word may split up text;
* cannot return null
*/
private String getTextOnNode(Element el)
{
String paraText = "";
// MS Word may split text under a <w:p> node; need to reassemble it before testing for a symbol
if (isMSWord)
{
paraText = reassembleText(el);
}
// other XML representations
else
{
paraText = XMLUtil.getText(el);
}
if (paraText == null) paraText = "";
return paraText;
}
/**
* reassemble text under an MS Word <w:p> element,
* which may have been split up e.g. to highlight spelling errors
* @param para
* @return concatenated text under all <w:r> elements;
* or "" if this is not a <w:p> element
*/
private String reassembleText(Element para)
{
String paraText = "";
if (para.getLocalName().equals("p"))
{
Vector<Element> rEls = XMLUtil.namedChildElements(para, "r");
for (int i = 0; i < rEls.size();i++)
{
Element tEl = XMLUtil.firstNamedChild(rEls.get(i), "t");
if (tEl != null) paraText = paraText + XMLUtil.getText(tEl);
}
}
return paraText;
}
//----------------------------------------------------------------------------------------------------------------------
// Substituting symbols in a Word XML from a class model instance or XML instance, to make the pre-edit word XML
//----------------------------------------------------------------------------------------------------------------------
/**
* populate the the pre-edit word xml from a mapped XML instance
* @param inputRoot
* @return
* @throws MapperException
*/
public Element fillFromInputXML(Element inputRoot) throws MapperException
{
setPreEditEcoreInstance(inputRoot);
return fillFromPreEditInstance();
}
/**
* create the Green model instance preEditObject from an instance of some mapped data source
* @param inputRoot
* @throws MapperException
*/
public void setPreEditEcoreInstance(Element inputRoot) throws MapperException
{
if (inputMappingSet == null) throw new MapperException("No input mapping set for TemplateFiller");
XOReader reader = new MDLXOReader(inputRoot, inputMappingSet, null);
Vector<objectToken> topObjectTokens = reader.getAllObjectTokens(ModelUtil.getQualifiedClassName(entryClass));
if (topObjectTokens.size() != 1) throw new MapperException("Input XML represents "
+ topObjectTokens.size() + " objects of the entry class " + entryClass.getName());
EMFInstanceFactory factory = new GenericEMFInstanceFactoryImpl();
// create the Ecore instance with the special URI so it is not saved anywhere
Resource res = factory.createModelInstance(reader, EMFInstanceFactoryImpl.DO_NOT_SAVE_URI(), topObjectTokens.get(0));
preEditObject = res.getContents().get(0);
}
/**
* populate the pre-edit word xml from an EMF Ecore instance
* whenever you find a symbol in the XML document, try to follow the path for the symbol in the
* model instance, and if you can, substitute the value.
* Otherwise substitute a small editing symbol like '-'
* @return root element of modified pre-edit Word XML document
* @throws MapperException
*/
public Element fillFromPreEditInstance() throws MapperException
{
Document doc = XMLUtil.makeOutDoc();
// import the whole word XML to another document, and word still reads it
Element preEditRoot = (Element)doc.importNode(templateRoot, true);
doc.appendChild(preEditRoot);
// change text values wherever a symbol has a value in the pre-edit instance
changeTextFromModel(preEditRoot, preEditObject);
return preEditRoot;
}
/**
* recursive descent of the template word xml, creating the pre-edit word xml
* @param doc
* @param el
* @param instance
* @return
* @throws MapperException
*/
private void changeTextFromModel(Element el, EObject instance) throws MapperException
{
String newText = NO_SYMBOL;
String symbol = getSymbolOnNode(el);
if (symbol != null) newText = findSubstituteString(symbol, instance);
// now newText cannot be null
// no symbol found - make no change to the element
if (newText.equals(NO_SYMBOL)) {}
// symbol found, but a value for it is not in the instance - replace it by the edit prompt
else if (newText.equals(SYMBOL_PATH_NOT_IN_INSTANCE)) {replaceOneSymbol(el, EDIT_PROMPT);}
// symbol found, and a value for it found in the instance; substitute the value
else {replaceOneSymbol(el,newText);}
// recursion through child elements
for (Iterator<Element> it = XMLUtil.childElements(el).iterator();it.hasNext();)
changeTextFromModel(it.next(), instance);
}
/**
*
* @param el
* @return
*/
private String getSymbolOnNode(Element el)
{
String symbol = null;
String paraText = removeSurroundingSpaces(getTextOnNode(el));
if (lookupTable.get(paraText) != null) symbol = paraText;
return symbol;
}
/**
*
* @param el
* @param replacement
*/
private void replaceOneSymbol(Element el, String replacement) throws MapperException
{
/* For MS Word, this must be a <w:p> element. Remove all its child elements,
* and replace them by one <w:r><w:t> nested element pair with the new text */
if (isMSWord)
{
NodeList nl = el.getChildNodes();
int len = nl.getLength();
// remove nodes in descending order, not re-evaluating the list length
for (int i = 0; i < len; i++)
{
int j = len - i - 1;
el.removeChild(nl.item(j));
}
String uri = el.getNamespaceURI(); // these elements are all in the 'w' namespace
Document doc = el.getOwnerDocument();
Element rChild = XMLUtil.NSElement(doc, "w", "r", uri);
Element tChild = XMLUtil.textNSElement(doc, "w", "t", uri, replacement);
rChild.appendChild(tChild);
el.appendChild(rChild);
}
else
{
throw new MapperException("Non MS Word case not yet supported");
}
}
/**
* substitute the value from an Ecore model instance, if the path for a symbol can be followed;
* if not, return the distinctive value SYMBOL_PATH_NOT_IN_INSTANCE.
* Never return null.
* @param symbol
* @param instance
* @return
*/
private String findSubstituteString(String symbol, EObject instance) throws MapperException
{
// result returned if you fail to follow the path
String newText = SYMBOL_PATH_NOT_IN_INSTANCE;
String path = lookupTable.get(symbol); // path cannot be null, and must be a valid path
String[] steps = pathSteps(path);
String firstStep = steps[0];
String instanceClass = instance.eClass().getName();
// this will have been detected earlier
if (!firstStep.equals(entryClass.getName())) throw new MapperException("Invalid first step of path");
if (!instanceClass.equals(firstStep))
throw new MapperException("Model instance has entry class " + instanceClass);
// follow the path in the instance
EObject current = instance;
for (int st = 1; st < steps.length; st++) if (current != null)
{
String step = steps[st];
EStructuralFeature feat = getFeature(current, step);
if (feat instanceof EReference)
{
// set the current object to the next along the path if you can follow it, or null otherwise
current = followRef(current, step);
}
// last link in the path must be an EAttribute to get a value
else if (feat instanceof EAttribute)
{
String attVal = (String)current.eGet(feat);
if (attVal != null) newText = attVal;
}
}
return newText;
}
/**
* @param instance
* @param step
* @return the EAttribute or ERefererence in a step
* @throws MapperException
*/
private EStructuralFeature getFeature(EObject instance, String step) throws MapperException
{
StringTokenizer st = new StringTokenizer(step,"[]");
String stem = st.nextToken();
EStructuralFeature feat = instance.eClass().getEStructuralFeature(stem);
if (feat == null) throw new MapperException("Missing feature in step '" + step + "'");
return feat;
}
/**
* Follow an EReference from the current object.
* If it delivers an EObject which passes the tests in the step, return that EObject,
* Otherwise return null
* @param current
* @param ref
* @param step
* @return
*/
private EObject followRef(EObject current, String step) throws MapperException
{
EObject res = null;
EReference ref = (EReference)getFeature(current, step);
if (ref.getUpperBound() == 1)
{
EObject obj = (EObject)current.eGet(ref);
if (testConditions(step,obj,0)) res = obj;
}
// multiple EReference; if any of the target objects satisfy all the tests, set the result to that; null otherwise
else if (ref.getUpperBound() == -1)
{
Object obj = current.eGet(ref);
if (obj instanceof EList<?>)
{
EList<?> listRef = (EList<?>)obj;
int pos = 0;
for (Iterator<?> it = listRef.iterator();it.hasNext();)
{
Object next = it.next();
if (next instanceof EObject)
{
if (testConditions(step,(EObject)next,pos)) res = (EObject)next;
}
else throw new MapperException("Multiple reference result is not an EList of EObjects");
pos++;
}
}
else throw new MapperException("Multiple reference result is not an EList of anything");
}
return res;
}
/**
* test a set of conditions on an EObject
* @param step
* @param obj
* @param pos
* @return
*/
private boolean testConditions(String step, EObject obj, int pos) throws MapperException
{
boolean result = true;
StringTokenizer parts = new StringTokenizer(step,"[]");
parts.nextToken(); // stem has already been dealt with
while (parts.hasMoreTokens())
{
String cond = parts.nextToken();
result = result && testOneCondition(cond,obj,pos);
}
return result;
}
/**
*
* @param cond
* @param obj
* @param pos
* @return
*/
private boolean testOneCondition(String cond, EObject obj, int pos) throws MapperException
{
boolean result = true;
StringTokenizer sides = new StringTokenizer(cond,"= ");
// no equality condition; can only be an Integer test of the position
if (sides.countTokens() == 1)
{
try
{
Integer iv = new Integer(cond);
result = (iv.intValue() == pos);
}
catch (Exception ex) {throw new MapperException("Condition value '" + cond + "' is not an Integer position");}
}
// equality condition; each side can be an attribute value or a constant
else if (sides.countTokens() == 2)
{
String[] values = new String[2];
for (int side = 0; side < 2; side++)
{
String toEval = sides.nextToken();
// constant in single quotes; strip them off
if ((toEval.startsWith("'")) && (toEval.endsWith("'")))
values[side] = toEval.substring(1,toEval.length()-1);
else
{
EStructuralFeature feat = obj.eClass().getEStructuralFeature(toEval);
if (feat == null) throw new MapperException("Condition side '" + toEval + "' is not a feature of the owner object");
if (feat instanceof EReference) throw new MapperException("Condition side '" + toEval + "' is an association");
values[side] = (String)obj.eGet(feat);
}
}
result = ((values[0] != null) && (values[1] != null) && (values[0].equals(values[1])));
}
else throw new MapperException("Too many '=' in condition '" + cond + "'");
return result;
}
//-------------------------------------------------------------------------------------------------------
// Putting values from the post-edit word XML into the model instance or an output XML instance
//-------------------------------------------------------------------------------------------------------
/**
*
* @param postEditRoot root element of word XML, after editing
* @param outputMappingSet
* @param nonXMLBody e.g. a base-64 encoded pdf
* @return
* @throws MapperException
*/
public Element makeDOMFromPostEditXML(Element postEditRoot, MappedStructure outputMappingSet, String nonXMLBody) throws MapperException
{
// make an EObject from the post-edit XML
EObject result = makeEObjectFromPostEditXML(postEditRoot);
// generate output XML from the extended Ecore object
objectGetter eOGetter = new EMFObjectGetter(classModel,result);
XMLWriter writer = outputMappingSet.getXMLWriter(eOGetter, classModel, new SystemMessageChannel(), false);
Element inWrappedResult = writer.makeXMLDOM();
Object outWrappedDoc = outputMappingSet.makeOutputObject(inWrappedResult, null);
if (!(outWrappedDoc instanceof Document))
throw new MapperException("Out-wrapped result is not an XML document");
Element rootElement = ((Document)outWrappedDoc).getDocumentElement();
if (nonXMLBody != null) addBody(rootElement,nonXMLBody);
return rootElement;
}
/**
* add a non-xml body, as base64 encoded text, at the end of the correct path in the non-coded CDA
* @param rootElement
* @param nonXMLBody
* @throws MapperException
*/
private void addBody(Element rootElement, String nonXMLBody) throws MapperException
{
Element current = rootElement;
Document doc = rootElement.getOwnerDocument();
String v3NamespaceURI = rootElement.getNamespaceURI();
StringTokenizer steps = new StringTokenizer(nonXMLBodyPath,"/");
// the root element tag name must be the first step of the path to the non XML body
String rootTag = steps.nextToken();
if (!rootTag.equals(rootElement.getLocalName()))
throw new MapperException("Root element name " + rootElement.getLocalName() + " does not match '" + rootTag + "'");
// follow the path to the non XML body, adding elements if you do not find them
while (steps.hasMoreTokens())
{
String step = steps.nextToken();
Element next = XMLUtil.firstNamedChild(current, step);
if (next == null)
{
next = XMLUtil.NSElement(doc,"", step, v3NamespaceURI);
current.appendChild(next);
}
current = next;
}
// add the non-XML body to the last element in the chain
Text textContent = doc.createTextNode(nonXMLBody);
current.appendChild(textContent);
}
/**
* @param postEditRoot
* @return an EObject made from the post-edit XML -
* possible merging it with the pre-Edit EObject
* (for cases where not all information in the pre-edit Ecore object is put in the pre-edit XML)
*/
public EObject makeEObjectFromPostEditXML(Element postEditRoot) throws MapperException
{
// if no pre-edit Ecore object has been made, make the minimal one
if (preEditObject == null) preEditObject = createModelObject(entryClass);
// clone the pre-edit Ecore object, so you can extend the clone from the post-edit XML
EObject result = cloneEObject(preEditObject);
// extend or modify the cloned pre-edit object from post-edit XML
addFromPostEditXML(postEditRoot,templateRoot, result);
return result;
}
/**
* recursive descent of the template XML and the edited XML,
* making additions to the result EObject wherever an edit has been made
* @param postEditEl
* @param templateEl
* @param result
* @throws MapperException
*/
private void addFromPostEditXML(Element postEditEl,Element templateEl, EObject result) throws MapperException
{
// deal with any edit on this node, if this node in the template contains a symbol
String symbol = getTextOnNode(templateEl);
if (isSyntacticSymbol(symbol))
{
String path = lookupTable.get(symbol);
String editedValue = getTextOnNode(postEditEl);
// if the edit prompt is still present, no edit has been made (not quite reliable, without looking at the pre-edit node!)
if (!editedValue.equals(EDIT_PROMPT)) addEditedValue(editedValue,path,result);
}
// recursive descent of word XML trees; currently makes strong assumptions that they match, or edits may be silently lost
Vector<Element> templateEls = XMLUtil.childElements(templateEl);
Vector<Element> postEditEls = XMLUtil.childElements(postEditEl);
if (templateEls.size() == postEditEls.size())
for (int i = 0; i < templateEls.size(); i++) if (i < postEditEls.size())
{
Element templateChild = templateEls.get(i);
Element postEditChild = postEditEls.get(i);
if (templateChild.getLocalName().equals(postEditChild.getLocalName()))
{
addFromPostEditXML(postEditChild,templateChild, result);
}
}
}
/**
* navigate and/or extend the result EObject to add or change an edited value
* @param editedValue
* @param path
* @param result
*/
private void addEditedValue(String editedValue,String path,EObject result) throws MapperException
{
String[] steps = pathSteps(path);
EObject current = result;
for (int ist = 1; ist < steps.length; ist++)
{
String step = steps[ist];
StringTokenizer parts = new StringTokenizer(step,"[]");
String stem = parts.nextToken();
EStructuralFeature feat = getFeature(current, stem);
if (feat == null) throw new MapperException("Cannot find feature '" + stem + "' in the class model.");
if (feat instanceof EReference)
{
EObject nextObj = followRef(current, step);
// if you cannot find an EObject for this step of the path, make one and add it to the current object feature
if (nextObj == null)
{
EClass nextClass = (EClass)((EReference)feat).getEType();
nextObj = createModelObject(nextClass);
addEObject(current,nextObj,(EReference)feat);
}
current = nextObj;
}
else if (feat instanceof EAttribute)
{
current.eSet(feat, editedValue);
}
}
}
/**
*
* @param theClass
* @return
*/
private EObject createModelObject(EClass theClass)
{
EPackage thePackage = theClass.getEPackage();
return thePackage.getEFactoryInstance().create(theClass);
}
/**
* add an EObject as a child on a feature of a parent
* @param parent
* @param child
* @param ref
*/
private void addEObject(EObject parent, EObject child, EReference ref)
{
if (ref.getUpperBound() == 1)
{
parent.eSet(ref, child);
}
else if (ref.getUpperBound() == -1)
{
EList<EObject> objs = (EList<EObject>)parent.eGet(ref);
if (objs == null) objs = new BasicEList<EObject>();
objs.add(child);
parent.eSet(ref, objs);
}
}
/**
* clone an EObject
* @param start
* @return
*/
private EObject cloneEObject(EObject start) throws MapperException
{
EObject root = createModelObject(entryClass);
extendClonedObject(root, start);
return root;
}
/**
* recursive cloning of an EObject - extend an empty EObject to have the same sub-structure as the start object
* @param resultObj an empty EObject
* @param startObj
*/
private void extendClonedObject(EObject resultObj, EObject startObj) throws MapperException
{
EClass resultClass = resultObj.eClass();
for (Iterator<EStructuralFeature> it = resultClass.getEAllStructuralFeatures().iterator();it.hasNext();)
{
EStructuralFeature feat = it.next();
if (feat instanceof EAttribute)
{
if (startObj.eGet(feat) != null) resultObj.eSet(feat, startObj.eGet(feat));
}
if (feat instanceof EReference)
{
EReference ref = (EReference)feat;
if (ref.getUpperBound() == 1)
{
EObject target = (EObject)startObj.eGet(ref);
if (target != null)
{
EObject resultTarget = createModelObject(target.eClass());
extendClonedObject(resultTarget,target);
resultObj.eSet(feat, resultTarget);
}
}
else if (ref.getUpperBound() == -1)
{
Object res = startObj.eGet(ref);
if (res instanceof EList<?>)
{
BasicEList<EObject> build = new BasicEList<EObject>();
EList<?> resList = (EList<?>)res;
for (Iterator<?> iu = resList.iterator(); iu.hasNext();)
{
Object nextObj = iu.next();
if (nextObj instanceof EObject)
{
EObject target = (EObject)nextObj;
EObject resultTarget = createModelObject(target.eClass());
extendClonedObject(resultTarget,target);
build.add(resultTarget);
}
else throw new MapperException("Another boring issue - not a list of EObjects");
}
if (build.size() > 0) resultObj.eSet(feat, build);
}
else throw new MapperException("Boring issue - ref result is not a list");
}
}
}
}
//------------------------------------------------------------------------------------------------------
// Utilities
//------------------------------------------------------------------------------------------------------
/**
* a path consists of steps separated by '.',
* but each step may have conditions in [], which may also contain '.'.
* Split the path into an array of steps
* @param path
* @return
*/
static String[] pathSteps(String path)
{
// substitute a key for each condition, remembering the condition
StringTokenizer st = new StringTokenizer(path,"[]",true);
Hashtable<String,String> conditions = new Hashtable<String,String>();
int c = 0;
String subs = "";
boolean inCond = false;
while (st.hasMoreTokens())
{
String next = st.nextToken();
if (next.equals("]")) inCond = false;
if (!inCond) subs = subs + next;
else if (inCond)
{
String key = "k" + c;
conditions.put(key, next);
subs = subs + key;
c++;
}
if (next.equals("[")) inCond = true;
}
// split the string into steps, and reinsert the conditions
StringTokenizer su = new StringTokenizer(subs,".");
int steps = su.countTokens();
String[] result = new String[steps];
int stepNo = 0;
while (su.hasMoreTokens())
{
String nStep = su.nextToken();
StringTokenizer sv = new StringTokenizer(nStep,"[]",true);
String step = "";
inCond = false;
while (sv.hasMoreTokens())
{
String part = sv.nextToken();
if (part.equals("]")) inCond = false;
if (!inCond) step = step + part;
else if (inCond) step = step + conditions.get(part);
if (part.equals("[")) inCond = true;
}
result[stepNo] = step;
stepNo++;
}
return result;
}
private void writeSteps(String[] steps, String path)
{
message("writing steps of path " + path);
for (int i = 0; i < steps.length; i++) message("Step: " + steps[i]);
}
private void message(String s) {System.out.println(s);}
}