package com.openMap1.mapper.mapping; import java.util.Vector; import java.util.Hashtable; import java.util.Enumeration; import java.util.Iterator; import org.eclipse.core.resources.IFile; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EReference; import org.w3c.dom.Element; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.core.XpthException; import com.openMap1.mapper.core.Xpth; import com.openMap1.mapper.core.namespace; import com.openMap1.mapper.core.NamespaceSet; import com.openMap1.mapper.core.ClassSet; import com.openMap1.mapper.writer.templateSet; import com.openMap1.mapper.reader.MDLXOReader; import com.openMap1.mapper.reader.XOReader; import com.openMap1.mapper.reader.objectRep; import com.openMap1.mapper.util.GenUtil; import com.openMap1.mapper.util.ModelUtil; import com.openMap1.mapper.util.EclipseFileUtil; import com.openMap1.mapper.util.Timer; import com.openMap1.mapper.util.messageChannel; import com.openMap1.mapper.util.SystemMessageChannel; import com.openMap1.mapper.util.XSLOutputFile; import com.openMap1.mapper.AssocMapping; import com.openMap1.mapper.ClassDetails; import com.openMap1.mapper.ElementDef; import com.openMap1.mapper.FixedPropertyValue; import com.openMap1.mapper.ImportMappingSet; import com.openMap1.mapper.MappedStructure; import com.openMap1.mapper.MapperPackage; import com.openMap1.mapper.Namespace; import com.openMap1.mapper.ObjMapping; import com.openMap1.mapper.ParameterClass; import com.openMap1.mapper.PropMapping; import com.openMap1.mapper.PropertyConversion; import com.openMap1.mapper.XSLTConversionImplementation; /** * <p>Core class for MDL-based applications. * Reads an MDL file, and makes the definitions of XML-object model * mappings available for other classes or subclasses.</p> * * @author Robert Worden * @version 2.06 */ public class MDLBase { private MappedStructure ms; public MappedStructure ms() {return ms;} private EPackage classModel = null; public EPackage classModel() {return classModel;} private boolean canFindClassModel = false; public boolean canFindClassModel() {return canFindClassModel;} private messageChannel mChan; public messageChannel mChan() {return mChan;} /** URI of XML Schema target namespace, if there is one schema */ public String targetURI; private String MDLFileName; /** name of file from which MDL mappings were read */ public String MDLFileName() {return MDLFileName;} private namespace mdlSpace = GenUtil.defaultMDLNamespace(); // should be overridden on reading the MDL file /** URI for the class model of the domain, and associated prefix which is used for MDL-specific elements */ public namespace mdlSpace() {return mdlSpace;} /** set the URI for the class model of the domain, and associated prefix which is used for MDL-specific elements */ public void setmdlSpace(namespace ns) {mdlSpace = ns;} ///to avoid repeated calls to imported mappings sets in representsObject private Hashtable<String,String> classesNotRepresented = new Hashtable<String,String>(); // private NamespaceSet NSSet; public NamespaceSet NSSet() {return NSSet;} /** * @return for each class name, a Vector of all its object mappings */ public Vector<objectMapping> objectMappingsByClassName(String className) { return (objectMappingsByClassName.get(className) == null) ? new Vector<objectMapping>() : objectMappingsByClassName.get(className) ; } protected Hashtable<String,Vector<objectMapping>> objectMappingsByClassName = new Hashtable<String,Vector<objectMapping>>(); // key = className /** * @return for each class name, a Vector of all its property mappings */ public Vector<propertyMapping> propertyMappingsByClassName(String className) { return (propertyMappingsByClassName.get(className)== null) ? new Vector<propertyMapping>() : propertyMappingsByClassName.get(className); } protected Hashtable<String,Vector<propertyMapping>> propertyMappingsByClassName = new Hashtable<String,Vector<propertyMapping>>(); // key = className /** * @return for each association name, a Vector of all association mappings */ public Vector<AssociationMapping> associationMappingsByName(String assocName) { return (associationMappingsByName.get(assocName) == null) ? new Vector<AssociationMapping>() : associationMappingsByName.get(assocName) ; } protected Hashtable<String,Vector<AssociationMapping>> associationMappingsByName = new Hashtable<String,Vector<AssociationMapping>>(); // key = association name /** * @param Class1Class2 a concatenated pair of class names 'class2$class2' * @return a Vector of all association mappings between them */ public Vector<AssociationMapping> associationMappingsByClass1Class2(String Class1Class2) { return (associationMappingsByClass1Class2.get(Class1Class2) == null) ? new Vector<AssociationMapping>() : associationMappingsByClass1Class2.get(Class1Class2) ; } protected Hashtable<String,Vector<AssociationMapping>> associationMappingsByClass1Class2 = new Hashtable<String,Vector<AssociationMapping>>(); // key = class1$class2 /** all mappings, keyed by string form of XPath - only for those mappings with definite paths */ public Hashtable<String,Vector<MappingTwo>> mappingsByDefinitePath = new Hashtable<String,Vector<MappingTwo>>(); /** all mappings, keyed by string form of XPath to parent node of the mapped node * - only for those mappings with definite paths. (used for efficient tree traversal of mappings in mappingConverter )*/ private Hashtable<String,Vector<MappingTwo>> mappingsByParentPath = new Hashtable<String,Vector<MappingTwo>>(); /** "1", keyed by string form of XPath to the node or any ancestor node * - only for those mappings with definite paths (used for an efficient test if there are any mappings in the subtree of a node*/ private Hashtable<String,String> hasMappingsInSubtree = new Hashtable<String,String>(); /** all mappings with indefinite paths */ protected Vector<MappingTwo> indefinitePathMappings = new Vector<MappingTwo>(); public Vector<MappingTwo> indefinitePathMappings() {return indefinitePathMappings;} private String MDLErrors; // string of all MDL load error messages private boolean hasIndefinitePaths = false; /** true if any mappings have indefinite XPaths with '//' steps */ public boolean hasIndefinitePaths() {return hasIndefinitePaths;} private int maxMappingDepth = 0; /** maximum XPath length of any mappings with definite XPaths */ public int maxMappingDepth() {return maxMappingDepth;} private int maxInnerDepth = 0; /** maximum XPath length inside the '//' step of any mappings with indefinite XPaths */ public int maxInnerDepth() {return maxInnerDepth;} protected Timer timer; //-------------------------------------------------------------------------------------- // Constructor //-------------------------------------------------------------------------------------- /** * constructor for use within Eclipse, where it is expected that the MappedStructure * enables you to find the class model (in the standard project file structure) */ public MDLBase(MappedStructure ms, messageChannel mChan) throws MapperException { this.mChan = mChan; if (mChan == null) this.mChan = new SystemMessageChannel(); this.ms = ms; initialise(); try{ classModel = ms.getClassModelRoot(); canFindClassModel = true; } catch (MapperException ex) {canFindClassModel = false;} } /** * constructor for use outside Eclipse, where it is not expected that the MappedStructure * enables you to find the class model, so the class model is supplied separately */ public MDLBase(MappedStructure ms, EPackage classModel, messageChannel mChan) throws MapperException { // System.out.println("Loading " + ms.getMappingSetName()); this.mChan = mChan; if (mChan == null) this.mChan = new SystemMessageChannel(); this.ms = ms; initialise(); this.classModel = classModel; // tell the MappedStructure what the class model is, so it does not go looking for it ms.setClassModelRoot(classModel); if (classModel != null) canFindClassModel = true; // System.out.println("Loaded"); } private void initialise() throws MapperException { if (timer == null) timer = new Timer("MDLBase"); timer.start(Timer.MAPPING_INITIALISE); setupNamespaces(); storeMappings(); addImpliedFilters(); setApexes(); timer.stop(Timer.MAPPING_INITIALISE); } /** * Give this XOReader 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; } public Timer timer() {return timer;} // read the namespaces from the mapping set private void setupNamespaces() throws MapperException { NSSet = new NamespaceSet(); if ((ms().getMappingParameters() != null) && (ms().getMappingParameters().getNameSpaces() != null)) for (Iterator<Namespace> it = ms().getMappingParameters().getNameSpaces().iterator(); it.hasNext();) { Namespace NS = it.next(); NSSet.addNamespace(new namespace(NS.getPrefix(),NS.getURL())); } } //-------------------------------------------------------------------------------------- // Support for imported mapping sets //-------------------------------------------------------------------------------------- /** * @return a unique identifier of this mapping set, used in avoiding infinite recursion */ public String mappingSetIdentifier() {return ms().eResource().getURI().toString();} /* Pool of MDLBase objects, shared between all MDLBases imported directly or indirectly * by some root MDLBase. */ protected Hashtable<String,MDLBase> mdlBasePool = new Hashtable<String,MDLBase>(); /** * Inform this MDLBase of the pool of MDLBases already defined * @param mdlBasePool a pool of MDLBase objects, shared between all MDLBases imported * directly or indirectly by some root MDLBase. * key = string form of URI of mapping set */ public void setMDLBasePool(Hashtable<String,MDLBase> mdlBasePool) {this.mdlBasePool = mdlBasePool;} /** * @param ims an importMappingSet node of the mappedStructure * @return the MDLBase for that node; get it from the pool of MDLBases * if you can, otherwise make it and add it to the pool * @throws MapperException */ private MDLBase getImportedMDLBase(ImportMappingSet ims) throws MapperException { MappedStructure impMS = ims.getImportedMappingSet(); if (impMS == null) return null; String URIString = impMS.eResource().getURI().toString(); String elName = ims.getImportingElement().getName(); String key = URIString + "_" + elName; MDLBase mdb = mdlBasePool.get(key); if (mdb == null) { /* set the name of the root element of the imported mapping set to be the name * of the importing element, before making the MDLBase */ impMS.getRootElement().setName(elName); mdb = new MDLBase(impMS,mChan()); // make the new MDLBase share in the pool of MDLBases mdb.setMDLBasePool(mdlBasePool); // record the new MDLBase in the pool mdlBasePool.put(key, mdb); } return mdb; } /** * @return all directly imported MDLBases, keyed by their unique identifiers (resource addresses), * concatenated with the importing element name */ public Hashtable<String,MDLBase> getDirectlyImportedMDLBases() throws MapperException { Hashtable<String,MDLBase> directImports = new Hashtable<String,MDLBase>(); for (Iterator<EObject> it = ModelUtil.getEObjectsUnder(ms(), MapperPackage.Literals.IMPORT_MAPPING_SET).iterator();it.hasNext();) { ImportMappingSet ims = (ImportMappingSet)it.next(); MDLBase impMDB = getImportedMDLBase(ims); // cached; may add to the cache String URIString = impMDB.mappingSetIdentifier(); String key = URIString + "_" + ims.getImportingElement().getName(); directImports.put(key,impMDB); } return directImports; } /** * @return all directly or indirectly imported MDLBases, keyed by their unique identifiers (addresses), * concatenated with the importing element name */ public Hashtable<String,MDLBase> getAllImportedMDLBases() { Hashtable<String,MDLBase> allImports = new Hashtable<String,MDLBase>(); try {addAllImportedMDLBases(allImports);} catch (Exception ex) {System.out.println("Exception in getAllImportedMDLBases: " + ex.getMessage());} return allImports; } /** * add to allImports any MDLBases imported by this one (directly or indirectly) * which are not in it already. * This recursion cannot diverge, because any MDLBase can only be added once * to allImports. * @param allImports a Hashtable of MDLBases * @throws MapperException */ public void addAllImportedMDLBases(Hashtable<String,MDLBase> allImports) throws MapperException { Hashtable<String, MDLBase> directImports = getDirectlyImportedMDLBases(); for (Enumeration<String> en = directImports.keys(); en.hasMoreElements();) { String key = en.nextElement(); // if any direct import of this mapping set is not already in the list.. if (allImports.get(key) == null) { MDLBase impMDB = directImports.get(key); // add the direct import allImports.put(key, impMDB); // add the direct and indirect imports of that mapping set impMDB.addAllImportedMDLBases(allImports); } } } /** * If this mapping set is used as an import by some other mapping set, then * it should (at present, 11/08) have just one parameter class. * Return the qualified name of this class, or throw a MapperException if there is more than * one parameter class; or null if there are no parameter classes * @return */ public String parameterClassName() throws MapperException { String pcName = null; int pClasses = 0; if (ms().getParameterClasses() != null) for (Iterator<ParameterClass> it = ms().getParameterClasses().iterator(); it.hasNext();) { pcName = it.next().getQualifiedClassName(); pClasses++; } if (pClasses > 1) throw new MapperException("Mapping set should not have " + pClasses + "parameter classes."); return pcName; } /** * If this mapping set is used as an import by some other mapping set, then * it should (at present, 11/08) have just one parameter class. * Return the ClasSet of this class, or throw a MapperException if there is more than * one parameter class; or null if there are no parameter classes * @return */ public ClassSet parameterClassSet() throws MapperException { ClassSet pSet = null; int pClasses = 0; if (ms().getParameterClasses() != null) for (Iterator<ParameterClass> it = ms().getParameterClasses().iterator(); it.hasNext();) { ParameterClass pc = it.next(); // the parameter class mapping always has subset "" pSet = new ClassSet(pc.getQualifiedClassName(),""); pClasses++; } if (pClasses > 1) throw new MapperException("Mapping set should not have " + pClasses + "parameter classes."); return pSet; } private ClassSet parameterValueClassSet = null; /** * When this mapping set is being used as an imported mapping set, record the ClassSet * of the mapping in a higher mapping set that imported it. * Used for XSLT generation. * @param pvc the importing ClassSet */ public void setParameterValueClassSet(ClassSet pvc) {parameterValueClassSet = pvc;} /** * @return the ClassSet of the mapping in a higher mapping set that imported this mapping set * (must have been previously recorded using setParameterValueClassSet). * Used for XSLT generation. */ public ClassSet getParameterValueClassSet() {return parameterValueClassSet;} //-------------------------------------------------------------------------------------- // Storing mappings //-------------------------------------------------------------------------------------- /** * create mappings and store them in structures for efficient retrieval. * Old-style mappings are wrappers around the new model mapper classes. * Do object mappings first, because the other may depend on them. */ private void storeMappings() throws MapperException { // first pass; object mappings for (Iterator<EObject> it = ms().eAllContents();it.hasNext();) { EObject next = it.next(); if (next instanceof ObjMapping) {storeObjectMapping((ObjMapping) next);} } // second pass; property mappings for (Iterator<EObject> it = ms().eAllContents();it.hasNext();) { EObject next = it.next(); // variable-value property mappings if (next instanceof PropMapping) {storePropertyMapping((PropMapping) next);} // fixed property mappings else if (next instanceof ObjMapping) for (Iterator<EObject> ip = next.eAllContents();ip.hasNext();) { EObject pm = ip.next(); if (pm instanceof FixedPropertyValue) {storeFixedPropertyMapping((FixedPropertyValue) pm);} } } // third pass: association mappings for (Iterator<EObject> it = ms().eAllContents();it.hasNext();) { EObject next = it.next(); if (next instanceof AssocMapping) {storeAssociationMapping((AssocMapping) next);} } } private void storeObjectMapping(ObjMapping oMap) throws MapperException { objectMapping om = new objectMapping(oMap,mChan); recordDepths(om); // record maximum path lengths putIn(objectMappingsByClassName,om.className(),om); storeByPath(om); } private void storePropertyMapping(PropMapping pMap) throws MapperException { propertyMapping pm = new propertyMapping(pMap,mChan); recordDepths(pm); // record maximum path lengths putIn(propertyMappingsByClassName,pm.className(),pm); storeByPath(pm); } private void storeFixedPropertyMapping(FixedPropertyValue fv) throws MapperException { propertyMapping pm = new propertyMapping(fv,mChan); recordDepths(pm); // record maximum path lengths putIn(propertyMappingsByClassName,pm.className(),pm); storeByPath(pm); } private void storeAssociationMapping(AssocMapping aMap) throws MapperException { AssociationMapping am = new AssociationMapping(aMap,mChan); // am.makeSimpleNestingRequired(); can't find this method putIn(associationMappingsByName,am.assocName(),am); String class12 = am.assocEnd(0).className() + "$" + am.assocEnd(1).className(); String class21 = am.assocEnd(1).className() + "$" + am.assocEnd(0).className(); putIn(associationMappingsByClass1Class2,class12,am); putIn(associationMappingsByClass1Class2,class21,am); storeByPath(am); } /* record all the nodes that have mappings or condition values on them in mappingsByDefinitePath record all the nodes that have mappings or condition values in their subtrees in hasMappingsInSubtree record all the nodes that have mappings on their child nodes in mappingsByParentPath */ private void storeByPath(MappingTwo m) throws MapperException { Xpth xp = m.nodePath(); if (xp.definite()) { // record the mapping node for this mapping, and that all its ancestors have a mapping in their subtree putIn(mappingsByDefinitePath,xp.stringForm(),m); recordAncestorNodes(xp); // record this mapping under all its condition nodes, and that all their ancestors have a mapping in their subtree for (int mi = 0; mi < m.allConditionPaths().size();mi++) { Xpth xq = (Xpth)m.allConditionPaths().elementAt(mi); /* If the mapping has a condition referring to the same node as the mapping itself, * do not record the mapping twice under that node */ if (!xp.stringForm().equals(xq.stringForm())) { putIn(mappingsByDefinitePath,xq.stringForm(),m); recordAncestorNodes(xq); } } // record that the parent of this node has a mapping on one of its child nodes Xpth xParent = xp.removeInnerStep(); putIn(mappingsByParentPath,xParent.stringForm(),m); } // FIXME - do these need to know about condition paths? else {indefinitePathMappings.addElement(m);} } // record that all ancestor nodes have mappings in their subtrees private void recordAncestorNodes(Xpth xp) throws XpthException { Xpth xq = xp; while (xq.size() > 0) { hasMappingsInSubtree.put(xq.stringForm(),"1"); xq = xq.removeInnerStep(); } } /** * true if there may be some mappings in the subtree of a node * @param xp Xpth XPath to the node * @return boolean */ public boolean mayHaveMappingsInSubtree(Xpth xp) { //if there are any mappings with indefinite paths, they may occur in any subtree return ((hasMappingsInSubtree.get(xp.stringForm()) != null)|(hasIndefinitePaths())); } /** Each value in Hashtable ht is a Vector of mappings, keyed by a String value. If there is not yet any entry for this key, put one in. Otherwise extend the vector already stored for this key. Inelegant repetition of the method I can't get it to sort out the generics otherwise*/ void putIn(Hashtable<String,Vector<MappingTwo>> ht, String key, MappingTwo mp) { Vector<MappingTwo> v = (Vector<MappingTwo>)ht.get(key); if (v == null) v = new Vector<MappingTwo>(); v.add(mp); ht.put(key,v); } void putIn(Hashtable<String,Vector<objectMapping>> ht, String key, objectMapping mp) { Vector<objectMapping> v= (Vector<objectMapping>)ht.get(key); if (v == null) v = new Vector<objectMapping>(); v.add(mp); ht.put(key,v); } void putIn(Hashtable<String,Vector<AssociationMapping>> ht, String key, AssociationMapping mp) { Vector<AssociationMapping> v= (Vector<AssociationMapping>)ht.get(key); if (v == null) v = new Vector<AssociationMapping>(); v.add(mp); ht.put(key,v); } void putIn(Hashtable<String,Vector<propertyMapping>> ht, String key, propertyMapping mp) { Vector<propertyMapping> v= (Vector<propertyMapping>)ht.get(key); if (v == null) v = new Vector<propertyMapping>(); v.add(mp); ht.put(key,v); } /* record the maximum path lengths for all mapping XPaths in the MDL. (a) for mappings with definite XPaths, record the maximum absolute depth (b) for mappings with indefinite XPaths, record the maximum inner depth inside the '//' step. */ private void recordDepths(MappingTwo m) throws MapperException { // if the mapping has a definite XPath from the root if (m.nodePath().definite()) { int depth = m.mappingDepth(); if (depth > maxMappingDepth) maxMappingDepth = depth; } else // if the mapping has an indefinite XPath from the root { hasIndefinitePaths = true; int depth = m.innerDepth(); if (depth > maxInnerDepth) maxInnerDepth = depth; } } //-------------------------------------------------------------------------------------- // access methods - general and templates //-------------------------------------------------------------------------------------- /** * return a Hashtable of all mappings that have a parent node to the mapped node, * given by the string form of its path. * * @param parentPath String: path to the parent node * @return Hashtable: all mappings that have a parent node, given by the string form of its path. * Key = String form of path to the mapping's own node (not the parent node) * Value = Vector of all mappings to that node */ public Hashtable<String,Vector<MappingTwo>> mappingsWithParent(String parentPath) { Vector<MappingTwo> mappings = mappingsByParentPath.get(parentPath); Hashtable<String,Vector<MappingTwo>> maps = new Hashtable<String,Vector<MappingTwo>>(); if (mappings != null) for (int m = 0; m < mappings.size(); m++) { MappingTwo mm = mappings.elementAt(m); putIn(maps,mm.nodePath().stringForm(),mm); } return maps; } /** * All mappings to a node * * @param path String: the path to a node * @return Vector: a Vector of all mappings to that node */ public Vector<MappingTwo> mappingsWithPath(String path) { Vector<MappingTwo> res = mappingsByDefinitePath.get(path); if (res == null) res = new Vector<MappingTwo>(); return res; } //-------------------------------------------------------------------------------------- // access methods - object mappings //-------------------------------------------------------------------------------------- /** Vector of all object mappings in this mapping set */ public Vector<objectMapping> objectMappingsVector() { Vector<objectMapping> res = new Vector<objectMapping>(); for (Enumeration<Vector<objectMapping>> e = objectMappingsByClassName.elements(); e.hasMoreElements();) { Vector<objectMapping> v = e.nextElement(); for (int i = 0; i < v.size(); i++) {res.addElement(v.elementAt(i));} } return res; } /** * There should be at most one object mapping for any class and subset. * Return it, or null if there is none * * @param cs ClassSet: a class and subset * @return objectMapping: the unique object mapping, or null */ public objectMapping namedObjectMapping(ClassSet cs) { objectMapping om = null; int found = 0; Vector<objectMapping> oms = objectMappingsByClassName.get(cs.className()); if (oms != null) for (int i = 0; i < oms.size(); i++) { objectMapping omt = oms.elementAt(i); if (omt.cSet().equals(cs)) {om = omt; found++;} } if (found > 1) {mChan.message("Error: MDL does not allow more than one object mapping for class " + cs.stringForm() + ". There are " + oms.size());} return om; } /** All object mappings for a class (different subsets); or an empty vector if there are none */ public Vector<objectMapping> objectMappings(String className) { Vector<objectMapping> res = new Vector<objectMapping>(); Vector<objectMapping> v = objectMappingsByClassName.get(className); if (v != null) {res = v;} return res; } /** * @param bareClassName an unqualified class name * @return qualified class names for all mapped classes in this mapping set * or any imports, with the unqualified class name */ public Vector<String> getQualifiedClassNames(String bareClassName) { Vector<String> qNames = getLocalQualifiedClassNames(bareClassName); for (Enumeration<MDLBase> en = getAllImportedMDLBases().elements();en.hasMoreElements();) { MDLBase impMDB = en.nextElement(); Vector<String> impNames = impMDB.getLocalQualifiedClassNames(bareClassName); for (Iterator<String> in = impNames.iterator();in.hasNext();) { String fullName = in.next(); if (!GenUtil.inVector(fullName, qNames)) qNames.add(fullName); } } return qNames; } public Vector<String> getLocalQualifiedClassNames(String bareClassName) { Vector<String> qNames = new Vector<String>(); for (Enumeration<String> en = objectMappingsByClassName.keys();en.hasMoreElements();) { String fullName = en.nextElement(); if ((ModelUtil.getBareClassName(fullName).equals(bareClassName)) && (!GenUtil.inVector(fullName, qNames))) qNames.add(fullName); } return qNames; } /** * @return all association mappings in this mapping set or any it imports */ public Vector<AssociationMapping> allAssociationMappings() { Vector<AssociationMapping> allMaps = associationMappings(); for (Enumeration<MDLBase> en = getAllImportedMDLBases().elements();en.hasMoreElements();) { MDLBase impMDB = en.nextElement(); for (Iterator<AssociationMapping> im = impMDB.associationMappings().iterator();im.hasNext();) allMaps.add(im.next()); } return allMaps; } /** * True if the XML directly represents objects of this class (not subclasses) * This method recurses down through all imported mapping sets, which might be expensive. * */ public boolean representsObject(String qualifiedClassName) { if (classesNotRepresented.get(qualifiedClassName) != null) return false; Hashtable<String,String> mappingSetsUsed = new Hashtable<String,String>(); return representsObject(qualifiedClassName, mappingSetsUsed); } /** * True if the XML directly represents objects of this class (not subclasses) * This method recurses down through all imported mapping sets, which might be expensive. * @param mappingSetsUsed Hashtable of mapping sets already tested, to avoid infinite recursion * */ public boolean representsObject(String qualifiedClassName, Hashtable<String,String> mappingSetsUsed) { timer.start(Timer.GET_METADATA); // record that this mapping set has been used, to avoid infinite recursion mappingSetsUsed.put(mappingSetIdentifier(), "1"); if (representsObjectLocally(qualifiedClassName)) return true; // if this mapping set does not represent the object, check out all imported mapping sets else { for (Iterator<EObject> it = ModelUtil.getEObjectsUnder(ms(), MapperPackage.Literals.IMPORT_MAPPING_SET).iterator();it.hasNext();) try { ImportMappingSet ims = (ImportMappingSet)it.next(); MDLBase impMDB = getImportedMDLBase(ims); //cached // recursive step, avoiding visiting the same mapping set twice if ((impMDB != null) && (mappingSetsUsed.get(impMDB.mappingSetIdentifier()) == null) && (impMDB.representsObject(qualifiedClassName,mappingSetsUsed))) return true; } catch (MapperException ex) {GenUtil.surprise(ex, "MDLBase.representsObject");} } // record that the class is not represented, so you won't have to work it out again classesNotRepresented.put(qualifiedClassName,"1"); timer.stop(Timer.GET_METADATA); return false; } /** * @param qualifiedClassName class name preceded by package name if non-empty * @return true if this mapping set represents objects of the class * (not subclasses) locally, not by importing */ public boolean representsObjectLocally(String qualifiedClassName) { return (objectMappingsByClassName.get(qualifiedClassName) != null); } /** Return a vector of qualified names of all classes represented by the XML */ public Vector<String> allClasses() { Vector<String> res = new Vector<String>(); for (Enumeration<String> e = objectMappingsByClassName.keys(); e.hasMoreElements();) {res.addElement(e.nextElement());} return res; } /** All subsets of a class represented in the XML. */ public Hashtable<String,ClassSet> subsets(String qualifiedClassName) { Hashtable<String,ClassSet> res = new Hashtable<String,ClassSet>(); Vector<objectMapping> v = objectMappingsByClassName.get(qualifiedClassName); if (v != null) for (int i = 0; i < v.size(); i++) { objectMapping om = v.elementAt(i); res.put(om.subset(),om.cSet()); } return res; } /** Returns a Vector of ClassSet objects - one for each subset of this class. */ public Vector<ClassSet> ClassSets(String qualifiedClassName) { Vector<ClassSet> res = new Vector<ClassSet>(); Vector<objectMapping> oms = objectMappingsByClassName.get(qualifiedClassName); if (oms != null) for (int i = 0; i < oms.size(); i++) {res.addElement((oms.elementAt(i)).cSet());} return res; } /** * get a named class from the ECore class model * @param qualifiedClassName the class name, preceded by the package name and '.' if non-empty * @return */ public EClass getNamedClass(String qualifiedClassName) { return ModelUtil.getNamedClass(classModel, qualifiedClassName); } /** Returns a Vector of ClassSet objects - one for each subclass which inherits from this class (including the class itself) and for each subset of that subclass which is represented. */ public Vector<ClassSet> subClassSets(String qualifiedClassName) { Vector<ClassSet> res = new Vector<ClassSet>(); EClass ec = getNamedClass(qualifiedClassName); if (ec != null) for (Iterator<EClass> it = ModelUtil.getAllClasses(classModel).iterator();it.hasNext();) { EClass next = it.next(); String qualifiedName = ModelUtil.getQualifiedClassName(next); if ((ec.isSuperTypeOf(next)) && (representsObject(qualifiedName))) { Vector<ClassSet> classSets = ClassSets(qualifiedName); for (Iterator<ClassSet> iw = classSets.iterator();iw.hasNext();) res.add(iw.next()); } } return res; } /** qualified names of all classes which inherit from this class,including the class itself, * and are represented in the mappings. * Return an empty vector if the class itself is not represented * and none of its subclasses are represented. */ public Vector<String> inheritors(String qualifiedClassName) { Vector<String> res = new Vector<String>(); EClass ec = getNamedClass(qualifiedClassName); if (ec != null) for (Iterator<EClass> it = ModelUtil.getAllClasses(classModel).iterator();it.hasNext();) { EClass next = it.next(); String qualifiedName = ModelUtil.getQualifiedClassName(next); if ((ec.isSuperTypeOf(next)) && (representsObject(qualifiedName))) res.add(qualifiedName); } return res; } //-------------------------------------------------------------------------------------- // primary key properties for a (class,subset) //-------------------------------------------------------------------------------------- /** * If there is any combination of properties * which constitute a unique identifier for objects of the (class,subset), and if the XML * is capable of returning those properties, return true * * @param cs ClassSet; the class and subset * @return boolean: true if the class and subset have a unique identifier * @throws MapperException */ public boolean hasUniqueIdentifier(ClassSet cs) throws MapperException {return (primaryKey(cs) != null);} /** If there is any combination of properties which constitute a unique identifier for objects of the (class,subset), and if the XML is capable of returning those properties, return a Vector of the property names. Otherwise return null. */ public Vector<String> primaryKey(ClassSet cs) throws MapperException { Vector<String> pKey = null; Vector<Vector<String>> candidates = uniqueIdentifiers(cs.className()); if (candidates != null) { boolean found = false; for (int i = 0; i < candidates.size(); i++) if (!found) { Vector<String> candidateKey = candidates.elementAt(i); boolean OK = true; for (int j = 0; j < candidateKey.size(); j++) { String field = candidateKey.elementAt(j); Vector<propertyMapping> pMaps = namedPropertyMappings(cs.className(), cs.subset(), field); if (pMaps.size() == 0) OK = false; } if (OK) { found = true; pKey = candidateKey; } } } return pKey; } //-------------------------------------------------------------------------------------- // access methods - property mappings //-------------------------------------------------------------------------------------- /** Vector of all property mappings */ public Vector<propertyMapping> propertyMappings() { Vector<propertyMapping> res = new Vector<propertyMapping>(); for (Enumeration<Vector<propertyMapping>> e = propertyMappingsByClassName.elements(); e.hasMoreElements();) { Vector<propertyMapping> v = e.nextElement(); for (int i = 0; i < v.size(); i++) {res.add(v.elementAt(i));} } return res; } /** * Return all property mappings for a given class, subset and property * * @param className String * @param subset String * @param propName String * @return Vector all property mappings for a given class, subset and property (There can be more than one through use of the multiWay attribute)l */ public Vector<propertyMapping> namedPropertyMappings(String className, String subset, String propName) { int i; propertyMapping pmt; Vector<propertyMapping> res, v; res = new Vector<propertyMapping>(); v = (Vector<propertyMapping>)propertyMappingsByClassName.get(className); if (v != null) for (i = 0; i < v.size(); i++) { pmt = v.elementAt(i); if ((subset.equals(pmt.subset())) && (propName.equals(pmt.propertyName()))) res.addElement(pmt); } return res; } /** * * @param cSet ClassSet: class and subset * @param propName String * @return Vector the property mappings for a given class, subset and property; * (there can be more than one because of multiway mappings - choice or redundant) */ public Vector<propertyMapping> getPropertyMappings(ClassSet cSet, String propName) { int i; Vector<propertyMapping> res = new Vector<propertyMapping>(); Vector<propertyMapping> v = propertyMappingsByClassName.get(cSet.className()); if (v != null) for (i = 0; i < v.size(); i++) { propertyMapping pmt = v.elementAt(i); if ((propName.equals(pmt.propertyName())) && (cSet.equals(pmt.cSet()))) {res.addElement(pmt);} } return res; } /** return the presumed-unique property mapping, or throw an exception if it is not unique. */ public propertyMapping getPresumedUniquePropertyMapping(ClassSet cSet, String propName) throws MapperException { propertyMapping pm = null; Vector<propertyMapping> v = getPropertyMappings(cSet,propName); if (v.size() == 1) {pm = v.elementAt(0);} else if (v.size() > 1) {throw new MapperException("Presumed unique property mapping for " + cSet.stringForm() + ":" + propName + " is not unique, but occurs " + v.size() + " times.");} return pm; } /** Hashtable keys = names of all properties and converted properties of this class represented directly by the XML for this subset. Hashtable values = "1". */ public Hashtable<String,String> allDirectPropertyReps(ClassSet cSet) { propertyMapping pm; Hashtable<String,String> res = new Hashtable<String,String>(); Vector<propertyMapping> v = propertyMappingsByClassName.get(cSet.className()); if (v != null) for (int i = 0; i < v.size(); i++) { pm = v.elementAt(i); if (pm.subset().equals(cSet.subset())) {res.put(pm.propertyName(),"1");} } return res; } /** true if the property in an actual property (not a converted property) * of one of the subsets of this class. * This method recurses through all imported mapping sets, so may be expensive. */ public boolean representsProperty(String className, String propName) { Hashtable<String,String> mdlBasesUsed = new Hashtable<String,String>(); return representsProperty(className,propName,mdlBasesUsed); } /** true if the property in an actual property (not a converted property) * of one of the subsets of this class. * This method recurses through all imported mapping sets, so may be expensive. * but because of the Hashtable, the recursion is guaranteed to be finite. */ public boolean representsProperty(String className, String propName,Hashtable<String,String> mdlBasesUsed) { timer.start(Timer.GET_METADATA); String ident = mappingSetIdentifier(); mdlBasesUsed.put(ident, "1"); boolean res = representsPropertyLocally(className,propName); // if this mapping set does not represent the property, check out all imported mapping sets if (!res) { for (Iterator<EObject> it = ModelUtil.getEObjectsUnder(ms(), MapperPackage.Literals.IMPORT_MAPPING_SET).iterator();it.hasNext();) try { ImportMappingSet ims = (ImportMappingSet)it.next(); if (!res) { MDLBase impMDB = getImportedMDLBase(ims); //cached if (impMDB != null) { String impIdent = impMDB.mappingSetIdentifier(); // recursive step (but do not check any MDLBase more than once) if ((mdlBasesUsed.get(impIdent)== null) && (impMDB.representsProperty(className,propName,mdlBasesUsed))) res = true; } } } catch (MapperException ex) {GenUtil.surprise(ex, "MDLBase.representsProperty");} } timer.stop(Timer.GET_METADATA); return res; } public boolean representsPropertyLocally(String className, String propName) { for (int i = 0; i < ClassSets(className).size(); i++) { ClassSet cs = (ClassSet)ClassSets(className).elementAt(i); if (allTruePropertyReps(cs).get(propName) != null) return true; } return false; } public boolean representsPropertyLocally(ClassSet cs, String propName) { return (allTruePropertyReps(cs).get(propName) != null) ; } /** * @param oRep an objectRep for a represented object * @param propName a property of the object * @return true if the property is represented. * Looks only in the mapping set * that represents the object, and those it imports directly. * This method assumes that a property can only be represented in the same mapping * set as the objectRep, or in one directly imported (where the object class * is the parameter class) * (assuming that mapping sets do not pass on their parameter classes as parameter * class values to other imported mapping sets) */ public boolean representsProperty(objectRep oRep, String propName) { // to avoid trying any mapping set more than once Hashtable<String,String> triedMappingSets = new Hashtable<String,String>(); XOReader reader = oRep.reader(); // a non-mapped reader imports no other reader, so no other reader can represent any property of an object it represents if (!(reader instanceof MDLXOReader)) return reader.representsProperty(oRep.className(), propName); MDLBase objectMDL = (MDLBase)reader; if (objectMDL.representsPropertyLocally(oRep.cSet(), propName)) return true; /* if the mapping set of the objectRep does not represent the property, * check out all directly imported mapping sets, once each. * You need only check an imported mapping set if its parameter class * is the class of the objectRep. */ else { for (Iterator<EObject> it = ModelUtil.getEObjectsUnder(objectMDL.ms(), MapperPackage.Literals.IMPORT_MAPPING_SET).iterator();it.hasNext();) try { ImportMappingSet ims = (ImportMappingSet)it.next(); if (ims.getParameterClassValues().get(0).getQualifiedClassName().equals(oRep.className())) { String elPath = ((ElementDef)ims.eContainer()).getPath(); MDLBase impMDB = objectMDL.getImportedMDLBase(ims); //cached if (impMDB == null) System.out.println("Null imported mappings at " + elPath); else if (triedMappingSets.get(impMDB.mappingSetIdentifier())== null) { // in the imported mapping set, the parameter class mapping has subset "" ClassSet cs = new ClassSet(oRep.className(),""); if (impMDB.representsPropertyLocally(cs,propName)) return true; triedMappingSets.put(impMDB.mappingSetIdentifier(), "1"); } } } catch (MapperException ex) {GenUtil.surprise(ex, "MDLBase.representsProperty");} } return false; } /** * Hashtable of names of all true properties of this class represented either directly by the XML for this subset, or by format conversion from directly represented converted properties */ public Hashtable<String,String> allTruePropertyReps(ClassSet cSet) { Vector<propertyConversion> w; //mChan.message("True properties of " + cSet.stringForm()); Hashtable<String,String> directs = allDirectPropertyReps(cSet); Hashtable<String,String> res = new Hashtable<String,String>(); Vector<propertyMapping> v = propertyMappingsByClassName.get(cSet.className()); if (v != null) for (int i = 0; i < v.size(); i++) { propertyMapping pm = v.elementAt(i); String pName = pm.propertyName(); if ((pm.subset().equals(cSet.subset())) && (!isPseudoProperty(cSet,pName))) {res.put(pName,"1");} } w = getPropertyConversions("in",cSet); if (w != null) for (int i = 0; i < w.size(); i++) { propertyConversion pc = w.elementAt(i); String pName = pc.resultProperty(); boolean hasArgs = true; for (int k = 0; k < pc.arguments().size(); k++) { String arg = (String)pc.arguments().elementAt(k); if (directs.get(arg) == null) hasArgs = false; } if (hasArgs) {res.put(pName,"1");} } return res; } /** Hashtable of XML converted properties in a class and subset; Include any converted property used in an in-conversion or out-conversion in any language. */ public Hashtable<String,String> pseudoProperties(ClassSet cSet) { Hashtable<String,String> pp = new Hashtable<String,String>(); Vector<propertyConversion> pcv = getAllPropertyConversions(cSet.className()); if (pcv != null) for (int i = 0; i < pcv.size(); i++) { propertyConversion pt = pcv.elementAt(i); if ((pt.sense().equals("out")) && (pt.cSet().equals(cSet))) {pp.put(pt.resultProperty(),"1");} if ((pt.sense().equals("in")) && (pt.cSet().equals(cSet))) for (int j = 0; j < pt.arguments().size(); j++) {pp.put((String)pt.arguments().elementAt(j),"1");} } return pp; } /** true if propName is a converted property in the class and subset */ public boolean isPseudoProperty(ClassSet cSet, String propName) {return (pseudoProperties(cSet).get(propName)!= null);} /** Vector of property conversions in a class (the empty Vector if there are none) */ public Vector<propertyConversion> getPropertyConversions(String sense, ClassSet cSet ) { Vector<propertyConversion> pCon = new Vector<propertyConversion>(); Vector<propertyConversion> pcv = getAllPropertyConversions(cSet.className()); if (pcv != null) for (int i = 0; i < pcv.size(); i++) { propertyConversion pt = pcv.elementAt(i); if ((pt.sense().equals(sense)) && (pt.cSet().equals(cSet))) pCon.add(pt); } return pCon; } /** The unique property conversion to convert in to a property in the object model, or null if there is none. */ public propertyConversion getInConversion(ClassSet cSet, String property) {return getConversion("in",cSet,property);} /** The unique property conversion to convert out to a converted property in the object model, or null if there is none. */ public propertyConversion getOutConversion(ClassSet cSet, String pseudoProperty) {return getConversion("out",cSet,pseudoProperty);} /** The unique property conversion to convert in to a property in the object model, or or out a converted property in the XML, or null if there is none. */ private propertyConversion getConversion(String sense, ClassSet cSet, String property) { // System.out.println(sense + " " + cSet.stringForm() + ":" + property); propertyConversion pc = null; Vector<propertyConversion> pcv = getAllPropertyConversions(cSet.className()); if (pcv != null) { for (int i = 0; i < pcv.size(); i++) { propertyConversion pt = pcv.elementAt(i); if ((pt.sense().equals(sense)) && (pt.cSet().equals(cSet)) && (pt.resultProperty().equals(property))) {pc = pt;} } } // if (pc != null) System.out.println("found"); return pc; } /** * get all property conversions defined for a class. * Some of these may have no implementations. * Note there may be different conversions for different subset mappings. * @param className qualified class name (package name first) * @return */ private Vector<propertyConversion> getAllPropertyConversions(String className) { Vector<propertyConversion> pcv = new Vector<propertyConversion>(); if (ms().getMappingParameters() != null) for (Iterator<ClassDetails> it = ms().getMappingParameters().getClassDetails().iterator();it.hasNext();) { ClassDetails cd = it.next(); if (cd.getQualifiedClassName().equals(className)) for (Iterator<PropertyConversion> iw = cd.getPropertyConversions().iterator();iw.hasNext();) { PropertyConversion pc = iw.next(); pcv.add(new propertyConversion(pc)); } } return pcv; } //-------------------------------------------------------------------------------------- // access methods - association mappings //-------------------------------------------------------------------------------------- /** Vector of all association mappings in this mapping set */ public Vector<AssociationMapping> associationMappings() { Vector<AssociationMapping> res = new Vector<AssociationMapping>(); for (Enumeration<Vector<AssociationMapping>> e = associationMappingsByName.elements(); e.hasMoreElements();) { Vector<AssociationMapping> v = e.nextElement(); for (int i = 0; i < v.size(); i++) {res.add(v.elementAt(i));} } return res; } /** all association mappings for a given association name, classes and subsets at either end. There can be more than one when the multiWay attribute is used */ public Vector<AssociationMapping> namedAssociationMappings(ClassSet cSet1,String assocName,ClassSet cSet2) { int i; Vector<AssociationMapping> v, res; AssociationMapping amt; res = new Vector<AssociationMapping>(); v = (Vector<AssociationMapping>)associationMappingsByName.get(assocName); if (v != null) for (i = 0; i < v.size(); i++) try { amt = v.elementAt(i); if ((cSet1.equals(amt.assocEnd(0).cSet())) && (cSet2.equals(amt.assocEnd(1).cSet()))) {res.addElement(amt);} } catch (MapperException ex) {GenUtil.surprise(ex, "MDLBase.namedAssociationMappings");} if (res.size() > 1) {checkMultiWay(res);} return res; } /** all association mappings for a given association name, class/subset at one end, * class/subset and role at the other end. * There can be more than one when the multiWay attribute is used */ public Vector<AssociationMapping> namedAssociationMappings(ClassSet cSet, ClassSet otherCSet, String assocName,String roleName) throws MapperException { int i; Vector<AssociationMapping> v, res; AssociationMapping amt; res = new Vector<AssociationMapping>(); v = (Vector<AssociationMapping>)associationMappingsByName.get(assocName); if ((roleName == null)| ((roleName != null) && (roleName.equals("")))) {return res;} if (v != null) for (i = 0; i < v.size(); i++) { amt = v.elementAt(i); if ((cSet.equals(amt.assocEnd(0).cSet())) && (otherCSet.equals(amt.assocEnd(1).cSet())) && (roleName.equals(amt.assocEnd(1).roleName()))) {res.addElement(amt);} else if ((cSet.equals(amt.assocEnd(1).cSet())) && (otherCSet.equals(amt.assocEnd(0).cSet())) && (roleName.equals(amt.assocEnd(0).roleName()))) {res.addElement(amt);} } if (res.size() > 1) {checkMultiWay(res);} return res; } /** If there is more than one association mapping for a given association name, class/subset at one end, and either role or class/subset at the other end, then they should all have the same non-empty multiway attribute, either all 'choice' or all 'redundant'. */ private boolean checkMultiWay(Vector<AssociationMapping> assocMaps) { AssociationMapping am = null; boolean success = true; boolean someChoice = false; boolean someRedundant = false; boolean someOther = false; for (int i = 0; i < assocMaps.size(); i++) { am = assocMaps.elementAt(i); String multi = am.multiWay(); if (multi.equals("choice")) someChoice = true; else if (multi.equals("redundant")) someRedundant = true; else someOther = true; } if (someOther) { mChan.message(assocMaps.size() + " mappings for association '" + am.fullName() + "' must all have multiWay = 'choice' or 'redundant'"); success = false; for (int i = 0; i < assocMaps.size(); i++) { am = assocMaps.elementAt(i); } } else if (someChoice & someRedundant) { mChan.message(assocMaps.size() + " mappings for association '" + am.fullName() + "' must all have multiWay = 'choice' or all have multiWay = 'redundant'; not a mixture."); success = false; } return success; } /** SHOULD NOT BE USED. get the one association mapping with given name, classes and subsets at either end. THIS METHOD IS WRONG BECAUSE OF MULTIWAY */ public AssociationMapping getPresumedUniqueAssociationMapping(ClassSet cSet1,String assocName, ClassSet cSet2) { AssociationMapping am; am = null; Vector<AssociationMapping> v = namedAssociationMappings(cSet1,assocName,cSet2); if (v.size() == 1) {am = v.elementAt(0);} else if (v.size() > 1) {mChan.message("Presumed unique association mapping [" + cSet1.stringForm() + "]" + assocName + "[" + cSet2.stringForm() + "] is not unique.");} return am; } /** * Get all association mappings between two classes, regardless of subsets * * @param classA String * @param classB String * @return Vector: all association mappings between the classes */ public Vector<AssociationMapping> getMappings(String classA, String classB) { // associationMappingsByClass has association mappings keyed by both orderings of class names Vector<AssociationMapping> assocs = associationMappingsByClass1Class2.get(classA + "$" + classB); if (assocs == null) assocs = new Vector<AssociationMapping>(); return assocs; } private Vector<String> assocVector(AssociationMapping am) { Vector<String> ass = new Vector<String>(); try{ ass.addElement(am.assocEnd(0).className()); ass.addElement(am.assocName()); ass.addElement(am.assocEnd(1).className()); } catch (MapperException ex) {GenUtil.surprise(ex, "MDLBase.assocVector");} return ass; } /** * true if the XML represents this association by name, with the classes at defined ends 1 and 2. * This method looks recursively in all imported mapping sets, so can be expensive. */ public boolean representsAssociation(String class1, String assocName, String class2) { Hashtable<String,String> mdlBasesUsed = new Hashtable<String,String>(); return representsAssociation(class1, assocName, class2, mdlBasesUsed); } /** * true if the XML represents this association by name, with the classes at defined ends 1 and 2. * This method looks recursively in all imported mapping sets, so can be expensive. */ public boolean representsAssociation(String class1, String assocName, String class2, Hashtable<String,String> mdlBasesUsed) { mdlBasesUsed.put(mappingSetIdentifier(), "1"); if (representsAssociationLocally(class1, assocName, class2)) return true; // if this mapping set does not represent the association, check out all imported mapping sets else { for (Iterator<EObject> it = ModelUtil.getEObjectsUnder(ms(), MapperPackage.Literals.IMPORT_MAPPING_SET).iterator();it.hasNext();) try { ImportMappingSet ims = (ImportMappingSet)it.next(); String elPath = ((ElementDef)ims.eContainer()).getPath(); MDLBase impMDB = getImportedMDLBase(ims); //cached if (impMDB == null) System.out.println("Null imported mappings at " + elPath); // recursive step, not using any mapping set more than once else if ((mdlBasesUsed.get(impMDB.mappingSetIdentifier())== null) && (impMDB.representsAssociation(class1,assocName,class2,mdlBasesUsed))) return true; } catch (MapperException ex) {GenUtil.surprise(ex, "MDLBase.representsAssociation");} } return false; } /** * true if the XML represents this association by name, with the classes at defined ends 1 and 2. */ public boolean representsAssociationLocally(String class1, String assocName, String class2) { for (int i = 0; i < associationMappings().size();i++) try { AssociationMapping am = (AssociationMapping)associationMappings().elementAt(i); if ((am.assocEnd(0).className().equals(class1)) && (am.assocEnd(1).className().equals(class2)) && (am.assocName().equals(assocName))) return true; } catch (MapperException ex) {GenUtil.surprise(ex, "MDLBase.representsAssociation");} return false; } /** true if the XML represents this association from class 1 by the named role to class 2, * with the classes at either end of the association. * Checks all imported mapping sets , so can be expensive. */ public boolean representsAssociationRole(String class1, String roleName, String class2) { if (representsAssociationRoleLocally(class1,roleName, class2)) return true; // if this mapping set does not represent the association, check out all imported mapping sets else { for (Enumeration<MDLBase> en = getAllImportedMDLBases().elements();en.hasMoreElements();) { MDLBase impMDB = en.nextElement(); if (impMDB.representsAssociationRoleLocally(class1,roleName,class2)) return true; } } return false; } /** true if the XML represents this association from class 1 by the named role to class 2, * with the classes at either end of the association. */ public boolean representsAssociationRoleLocally(String class1, String roleName, String class2) { return (getAssociationRoleMappingsLocal(class1,roleName,class2).size() > 0); } /** true if the XML represents this association from class 1, subset 1 by the named role to class 2, * with the classes at either end of the association. */ public boolean representsAssociationRoleLocally(String class1, String subset1, String roleName, String class2) { boolean found = false; String mappingKey = ""; for (int e= 0; e < 2; e++) { if (e == 0) mappingKey = class1 + "$" + class2; if (e == 1) mappingKey = class2 + "$" + class1; // retrieve only mappings that get the two class names right Vector<AssociationMapping> possibles = associationMappingsByClass1Class2(mappingKey); for (int p = 0; p < possibles.size(); p++) try { AssociationMapping am = possibles.get(p); associationEndMapping aem = am.assocEnd(1-e); // the end to get to the other class // check the subset and role name if ((am.assocEnd(e).subset().equals(subset1)) && (aem.roleName().equals(roleName))) found = true; } catch (MapperException ex) {GenUtil.surprise(ex,"representsAssociationRoleLocally");} } return found; } /** * * @param className * @param role * @param otherClass * @return all association mappings with the start class, role , and end class */ public Vector<AssociationMapping> getAssociationRoleMappingsLocal(String className, String role, String otherClass) { Vector<AssociationMapping> mappings = new Vector<AssociationMapping>(); String mappingKey = ""; for (int e= 0; e < 2; e++) { if (e == 0) mappingKey = className + "$" + otherClass; if (e == 1) mappingKey = otherClass + "$" + className; Vector<AssociationMapping> possibles = associationMappingsByClass1Class2(mappingKey); for (int p = 0; p < possibles.size(); p++) try { AssociationMapping am = possibles.get(p); associationEndMapping aem = am.assocEnd(1-e); // the end to get to the other class if (aem.roleName().equals(role)) mappings.add(am); } catch (MapperException ex) {GenUtil.surprise(ex,"getAssociationRoleMappings");} } return mappings; } /** true if the XML represents this association from class 1 by the named role to class 2, * with named subsets * with the classes at either end of the association. */ public boolean representsAssociationRoleLocally(String class1, String subset1, String roleName, String class2, String subset2) { Vector<AssociationMapping> possibles = getAssociationRoleMappingsLocal(class1, roleName, class2); for (int i = 0; i < possibles.size();i++) try { AssociationMapping am = possibles.get(i); for (int e = 0; e < 2; e++) { if ((am.assocEnd(e).className().equals(class1)) && (am.assocEnd(e).subset().equals(subset1)) && (am.assocEnd(1-e).className().equals(class2)) && (am.assocEnd(1-e).subset().equals(subset2)) && (am.assocEnd(1-e).roleName().equals(roleName))) return true; } } catch (MapperException ex) {GenUtil.surprise(ex, "MDLBase.representsAssociationRoleLocally");} return false; } /** * @param oRep an objectRep for a represented object * @param roleName an association from the object * @param class2 the class at the other end * @return true if the association is represented, with the subset in the objectRep. * Looks only in the mapping set * that represents the object, and those it imports directly. * This method assumes that an association can only be represented in the same mapping * set as the objectRep, or in one directly imported (where the object class * is the parameter class) * (assuming that mapping sets do not pass on their parameter classes as parameter * class values to other imported mapping sets) */ public boolean representsAssociationRole(objectRep oRep, String roleName, String class2) { String className = oRep.className(); // to avoid trying any mapping set more than once Hashtable<String,String> triedMappingSets = new Hashtable<String,String>(); XOReader reader = oRep.reader(); if (!(reader instanceof MDLXOReader)) return reader.representsAssociationRole(className, roleName, class2); MDLBase objectMDL = (MDLBase)reader; if (objectMDL.representsAssociationRoleLocally(className,oRep.subset(),roleName, class2)) return true; /* if the mapping set of the object mapping does not represent the association, * check out directly imported mapping sets. * You need only try an imported mapping set if its parameter class matches * the class of the objectRep. In the imported mapping set, the subset is "" */ else { for (Iterator<EObject> it = ModelUtil.getEObjectsUnder(objectMDL.ms(), MapperPackage.Literals.IMPORT_MAPPING_SET).iterator();it.hasNext();) try { ImportMappingSet ims = (ImportMappingSet)it.next(); if (ims.getParameterClassValues().get(0).getQualifiedClassName().equals(className)) { MDLBase impMDB = objectMDL.getImportedMDLBase(ims); //cached if (triedMappingSets.get(impMDB.mappingSetIdentifier()) == null) { if (impMDB.representsAssociationRoleLocally(className,"",roleName,class2)) return true; triedMappingSets.put(impMDB.mappingSetIdentifier(),"1"); } } } catch (MapperException ex) {GenUtil.surprise(ex, "MDLBase.representsAssociationRole");} } return false; } /** return a Hashtable of all associations represented by the XML in which this class is on one side of the association. side = 1 for left-hand side, 2 for the right-hand side Each association is represented by a Vector (class1,association, class2) */ public Hashtable<String,Vector<String>> associations(String className,int side) throws MapperException { int i; Hashtable<String,Vector<String>> res = new Hashtable<String,Vector<String>>(); if ((side > 0) & (side < 3)) for (Enumeration<Vector<AssociationMapping>> e = associationMappingsByName.elements(); e.hasMoreElements();) { Vector<AssociationMapping> v = e.nextElement(); if (v != null) for (i = 0; i < v.size(); i++) { AssociationMapping am = v.elementAt(i); if (am.assocEnd(side-1).className().equals(className)) { Vector<String> ass = assocVector(am); // remove all duplicates, assuming there are no spaces in the names res.put(GenUtil.concatenate(ass," "),ass); } } } else throw new MapperException("Error when listing associations for class '" + className + "'; side must be 1 or 2."); return res; } /** Vector of names of all classes that inherit from this one, including itself. * (One of the methods inheritors and allInheritors is redundant.) */ public Vector<String> allInheritors(String className) { return inheritors(className); } /** Return a Vector of vectors. Each one is a Vector of property names which together form a unique identifier for objects of the class - including those unique identifiers it inherits.*/ public Vector<Vector<String>> uniqueIdentifiers(String className) { return ModelUtil.uniqueIdentifiers(className,classModel()); } //-------------------------------------------------------------------------------------- // inclusion filters in the semantic model //-------------------------------------------------------------------------------------- /* record in the objectMappings the extra inclusion filters implied by fixed properties and 'required' associations. */ void addImpliedFilters() { objectMapping om; int i,j; /* if an object is represented as having a property with a fixed value, that implies an inclusion filter that the object must have that value of the property. */ for (Enumeration<Vector<propertyMapping>> e = propertyMappingsByClassName.elements(); e.hasMoreElements();) { Vector<propertyMapping> v = e.nextElement(); for (i = 0; i < v.size(); i++) { propertyMapping pm = v.elementAt(i); if (pm.fixed()) { om = namedObjectMapping(pm.getClassSet()); filterProp fp = new filterProp(mChan,pm.fixedPropertyValue()); om.addInclusionFilter(fp); } } } /* if an association is represented as being required by the object at either end, that implies an inclusion filter that the object must have the association. */ for (Enumeration<Vector<AssociationMapping>> e = associationMappingsByName.elements(); e.hasMoreElements();) { Vector<AssociationMapping> v = e.nextElement(); for (i = 0; i < v.size(); i++) { AssociationMapping am = v.elementAt(i); for (j = 0; j < 2; j++) try { associationEndMapping aem = am.assocEnd(j); if (aem.required()) { filterAssoc fs = new filterAssoc(mChan,am,j); om = namedObjectMapping(aem.getClassSet()); if (om != null) om.addInclusionFilter(fs); } } catch (MapperException ex) {GenUtil.surprise(ex, "MDLBase.addImpliedFilters");} } } } //------------------------------------------------------------------------------------- // Massaging names of classes, properties and associations // gaplessForm converts 'purchasing unit' to 'purchasingUnit'. //------------------------------------------------------------------------------------- String noGap(String s) {return GenUtil.gaplessForm(s);} //------------------------------------------------------------------------------------- // Messages and Exceptions //------------------------------------------------------------------------------------- void exceptionMessage(String s) { //append this mChan.message to the string of all MDL errors MDLErrors = MDLErrors + s + "; "; // write it out somewhere mChan.message(s); } //------------------------------------------------------------------------------------- // Retrieval of mapping information //------------------------------------------------------------------------------------- void writeAssocMappings() { mChan.message("Writing all association mappings: " ); for (Enumeration<Vector<AssociationMapping>> e = associationMappingsByName.elements(); e.hasMoreElements();) { Vector<AssociationMapping> v = e.nextElement(); for (Iterator<AssociationMapping> it = v.iterator();it.hasNext();) try { AssociationMapping amt = it.next(); mChan.message("[" + amt.assocEnd(0).className() + ",'" + amt.assocEnd(0).subset() + "']" + amt.assocName() + "[" + amt.assocEnd(1).className() + ",'" + amt.assocEnd(1).subset() + "']"); } catch (MapperException ex) {GenUtil.surprise(ex, "MDLBase.writeAssocMappings");} } } /** true if the node at path xp has any mappings or condition values; * but done by exhaustive listing of all mappings, so slow */ public boolean hasMappings(Xpth xp) { int i,m,side; boolean mapped = false; for (Enumeration<Vector<objectMapping>> e = objectMappingsByClassName.elements(); e.hasMoreElements();) { Vector<objectMapping> v = e.nextElement(); for (i = 0; i < v.size(); i++) { objectMapping om = v.elementAt(i); if (om.nodePath().equalPath(xp)) mapped = true; for (m = 0; m < om.whenConditions().size();m++) if (om.whenCondition(m).rootToLeftValue().equalPath(xp)) mapped = true; } } if (!mapped) for (Enumeration<Vector<propertyMapping>> e = propertyMappingsByClassName.elements(); e.hasMoreElements();) { Vector<propertyMapping> v = e.nextElement(); for (i = 0; i < v.size(); i++) { propertyMapping pm = v.elementAt(i); if (pm.nodePath().equalPath(xp)) mapped = true; for (m = 0; m < pm.whenConditions().size();m++) if (pm.whenCondition(m).rootToLeftValue().equalPath(xp)) mapped = true; for (m = 0; m < pm.linkConditions().size();m++) { if (pm.linkCondition(m).rootToLeftValue().equalPath(xp)) mapped = true; if (pm.linkCondition(m).rootToRightValue().equalPath(xp)) mapped = true; } } } if (!mapped) for (Enumeration<Vector<AssociationMapping>> e = associationMappingsByName.elements(); e.hasMoreElements();) { Vector<AssociationMapping> v = e.nextElement(); for (i = 0; i < v.size(); i++) { AssociationMapping am = v.elementAt(i); if (am.nodePath().equalPath(xp)) mapped = true; for (m = 0; m < am.whenConditions().size();m++) if (am.whenCondition(m).rootToLeftValue().equalPath(xp)) mapped = true; for (m = 0; m < am.linkConditions().size();m++) { if (am.linkCondition(m).rootToLeftValue().equalPath(xp)) mapped = true; if (am.linkCondition(m).rootToRightValue().equalPath(xp)) mapped = true; } for (side = 0; side < 2; side++) try { associationEndMapping aem = am.assocEnd(side); for (m = 0; m < aem.linkConditions().size();m++) { if (aem.linkCondition(m).rootToLeftValue().equalPath(xp)) mapped = true; if (aem.linkCondition(m).rootToRightValue().equalPath(xp)) mapped = true; } } catch (MapperException ex) {GenUtil.surprise(ex, "MDLBase.hasMappings");} } } return mapped; } //------------------------------------------------------------------------------------- // Apexes of ClassSets //------------------------------------------------------------------------------------- // key - string form of ClassSet. Value = Xpth to apex Hashtable<String, Xpth> apexes = new Hashtable<String, Xpth>(); /** the apex of a ClassSet is the lowest node which has all mappings involving the ClassSet beneath it - i.e it is the node at which an object of the ClassSet should be put into the context when writing an output document in a translation. Mappings involving the ClassSet include: - the object mapping for the ClassSet - object mappings for other ClassSets which use this ClassSet in an association inclusion filter - property mappings for any property of the clasSet - association mappings with this ClassSet at either end. The apex can only be defined for mappings with definite paths from the root. Currently write a warning mChan.message for any indefinite paths in mappings. */ public void setApexes() throws XpthException { for (Enumeration<Vector<objectMapping>> en = objectMappingsByClassName.elements(); en.hasMoreElements();) { Vector<objectMapping> oMaps = en.nextElement(); for (int i = 0; i < oMaps.size(); i++) { objectMapping om = oMaps.elementAt(i); addApex(om.cSet(),om.nodePath()); for (int f = 0; f < om.filterAssocs().size(); f++) { filterAssoc fa = (filterAssoc)om.filterAssocs().elementAt(f); addApex(fa.cSet1(),om.nodePath()); addApex(fa.cSet2(),om.nodePath()); } } } for (Enumeration<Vector<propertyMapping>> en = propertyMappingsByClassName.elements(); en.hasMoreElements();) { Vector<propertyMapping> pMaps = en.nextElement(); for (int i = 0; i < pMaps.size(); i++) { propertyMapping pm = pMaps.elementAt(i); addApex(pm.cSet(),pm.nodePath()); } } for (Enumeration<Vector<AssociationMapping>> en = associationMappingsByName.elements(); en.hasMoreElements();) { Vector<AssociationMapping> aMaps = en.nextElement(); for (int i = 0; i < aMaps.size(); i++) try { AssociationMapping am = aMaps.elementAt(i); addApex(am.assocEnd(0).cSet(),am.nodePath()); addApex(am.assocEnd(1).cSet(),am.nodePath()); } catch (MapperException ex) {GenUtil.surprise(ex, "MDLBase.setApexes");} } } /* add an apex for a new ClassSet, or if there is already an apex, raise it to the lowest common node of the two paths. */ private void addApex(ClassSet cSet, Xpth path) throws XpthException { Xpth next = path; Xpth previous = apexes.get(cSet.stringForm()); if (previous != null) { next = previous.commonPath(path); } apexes.put(cSet.stringForm(),next); } public void writeApexes() { for (Enumeration<String> en = apexes.keys();en.hasMoreElements();) { String csName = en.nextElement(); Xpth apex = apexes.get(csName); mChan.message("ClassSet: '" + csName + "' has apex: '" + apex.stringForm() + "'"); } } public Xpth getApex(ClassSet cSet) {return apexes.get(cSet.stringForm());} //------------------------------------------------------------------------------------------- // For use by EMFInstanceFactory //------------------------------------------------------------------------------------------- /** * @return all object mappings in this mapping set (not imported) that * are not inside a containment relation to some other class * which also has an object mapping to the top mapping set. * Key = string form of the [class,subset]. * Returns a empty Vector if the outer class is nested inside itself */ public Vector<ClassSet> outerObjectClassSets() { Vector<ClassSet> result = new Vector<ClassSet>(); Hashtable<String,String> classesRemoved = new Hashtable<String,String>(); Hashtable<String,Vector<String>> subClassNames = subClassNames(); // Accumulate all classes not yet removed, and all classes to be removed for (Iterator<objectMapping> it = objectMappingsVector().iterator();it.hasNext();) { objectMapping om = it.next(); // if this class is not yet on the list to be removed... if (classesRemoved.get(om.className())== null) { result.add(om.cSet()); // all object mappings have different classSets noteClassesInside(om.className(),classesRemoved,subClassNames); } } // remove any classes that now need to be removed, because outer classes came after them Vector<ClassSet> realResult = new Vector<ClassSet>(); for (Iterator<ClassSet> ic = result.iterator();ic.hasNext();) { ClassSet cs = ic.next(); if (classesRemoved.get(cs.className()) == null) realResult.add(cs); } return realResult; } /** * Note which classes should not be in the list of outer classes, * because their instances are nested inside instances of some outer mapped class * @param className * @param classesRemoved * @param subClassNames pre-prepared Vectors of subclasses for each class */ private void noteClassesInside(String className, Hashtable<String,String> classesRemoved, Hashtable<String,Vector<String>> subClassNames) { EClass theClass = ModelUtil.getNamedClass(classModel(), className); if (theClass == null) return; for (Iterator<EReference> ir = theClass.getEAllReferences().iterator();ir.hasNext();) { EReference ref = ir.next(); if ((ref.isContainment()) && (ref.getEType() != null) && (ref.getEType() instanceof EClass)) { EClass child = (EClass)ref.getEType(); String childName = ModelUtil.getQualifiedClassName(child); // follow direct containments down if (classesRemoved.get(childName) == null) { classesRemoved.put(childName, "1"); noteClassesInside(childName,classesRemoved,subClassNames); } // follow containments of subclasses down Vector<String> subClasses = subClassNames.get(childName); if (subClasses != null) for (Iterator<String> ic = subClasses.iterator();ic.hasNext();) { String subChildName = ic.next(); if (classesRemoved.get(subChildName) == null) { classesRemoved.put(subChildName, "1"); noteClassesInside(subChildName,classesRemoved,subClassNames); } } } } } /** * @return a Hashtable with key = qualified class name, value = Vector of * qualified names of all its subclasses (including itself) , direct or indirect */ Hashtable<String,Vector<String>> subClassNames() { Hashtable<String,Vector<String>> subClassNames = new Hashtable<String,Vector<String>>(); for (Iterator<EClass> it = ModelUtil.getAllClasses(classModel()).iterator();it.hasNext();) { EClass subClass = it.next(); String subClassName = ModelUtil.getQualifiedClassName(subClass); for (Iterator<EClass> ix = subClass.getEAllSuperTypes().iterator();ix.hasNext();) { EClass superClass = ix.next(); String superClassName = ModelUtil.getQualifiedClassName(superClass); Vector<String> subClasses = subClassNames.get(superClassName); if (subClasses == null) subClasses = new Vector<String>(); subClasses.add(subClassName); subClassNames.put(superClassName, subClasses); } } return subClassNames; } //------------------------------------------------------------------------------------------- // XSLT templates //------------------------------------------------------------------------------------------- // key = URI of template set; value = the template set private Hashtable<String,templateSet> templateSets; // key = URI of template set; value = true if it has been successfully read in private Hashtable<String,Boolean> templateSetValid; // key = template name, without prefix; value = containing template set, if successfully read in private Hashtable<String,templateSet> containingTemplateSet; /** * read in all property conversion templates defined for the mapping set, and write warnings if * anything is wrong: * - template file missing or cannot be opened * - template appears in more than one template file * Missing templates are detected later. */ public void setUpTemplates() { templateSets = new Hashtable<String,templateSet>(); templateSetValid = new Hashtable<String,Boolean>(); containingTemplateSet = new Hashtable<String,templateSet>(); for (Iterator<XSLTConversionImplementation> it = xslConversions().iterator();it.hasNext();) { XSLTConversionImplementation xc = it.next(); String uri = xc.getTemplateFileURI(); if (uri == null) writeWarning("No XSL file has been specified for template '" + xc.getTemplateName() + "'"); // if we have not already tried to read this template set, do so else if (templateSetValid.get(uri) == null) { try { IFile tempFile = EclipseFileUtil.getFile(uri); templateSet tempSet = new templateSet(tempFile); templateSets.put(uri, tempSet); templateSetValid.put(uri,new Boolean(tempSet.valid())); if (tempSet.valid()) { for (Iterator<String> in = tempSet.templates().keySet().iterator();it.hasNext();) { String tempName = in.next(); if (containingTemplateSet.get(tempName) != null) { writeWarning("Template '" + tempName + "' is defined in more than one template file."); } else containingTemplateSet.put(tempName, tempSet); } } } // problems in getting one template file are not fatal, so no exception is thrown catch (Exception ex) { writeWarning("Could not read conversion template file at '" + uri + "'; " + ex.getMessage()); templateSetValid.put(uri,new Boolean(false)); } } } } private void writeWarning(String s) {System.out.println(s);} private Vector<XSLTConversionImplementation> xslConversions() { Vector<XSLTConversionImplementation> xc = new Vector<XSLTConversionImplementation>(); for (Iterator<EObject> it = ModelUtil.getEObjectsUnder(ms(), MapperPackage.Literals.XSLT_CONVERSION_IMPLEMENTATION).iterator();it.hasNext();) { xc.add((XSLTConversionImplementation)it.next()); } return xc; } /** * When generating an XSLT translation, * all XSLT template names have a prefix "in_" or "out_" added to them * to avoid name clashes between in-conversion templates of the input XML * and out-conversion templates of the output MDL. * This prefix must be added only once. * * @param name name of the template, with no prefix * @param prefix the prefix; must be 'in_' or 'out_' * @param xout XSL File in which the full translation, including conversion templates, * is being assembled * @return the template node - never null * @exception MapperException if anything goes wrong; this exception must be trapped immediately, * to write valid XSLT without the conversion template */ public Element getConversionTemplate(String name, String prefix, XSLOutputFile xout) throws MapperException { Element template = null; templateSet tempSet = containingTemplateSet.get(name); if ((!(prefix.equals("in_"))) && (!(prefix.equals("out_")))) {throw new MapperException("Invalid prefix added to name of XSLT template: '" + prefix + "'");} else if (tempSet == null) {throw new MapperException("Could not open template file containing XSLT template '" + name + "' in supplied template file.");} else if (tempSet.getTemplate(name) == null) {throw new MapperException("Cannot find XSLT template '" + name + "' in supplied template file.");} else { Element temp = tempSet.getTemplate(name); // deep import of the template into the output document boolean deep = true; template = (Element)xout.outDoc().importNode(temp,deep); // test to avoid adding prefix twice if (template.getAttribute("name").equals(name)) {template.setAttribute("name",prefix + name);} } return template; } }