package com.openMap1.mapper.writer; import java.util.Vector; import java.util.Hashtable; import java.util.Enumeration; import java.util.Iterator; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EReference; import org.w3c.dom.Node; import com.openMap1.mapper.core.MapperException; import com.openMap1.mapper.reader.XOReader; import com.openMap1.mapper.reader.objectToken; import com.openMap1.mapper.reader.SimpleObjectRep; import com.openMap1.mapper.structures.MappableAssociation; import com.openMap1.mapper.util.ModelUtil; /** * SimpleObjectGetter provides objects, properties and links from a class model * instance to an XMLWriter, having allowed this class model instance to be set up first * by atomic calls to add objects, properties and links. * @author robert * */ public class SimpleObjectGetter implements objectGetter { /* key = className; value = a Hashtable whose key is the object key and whose value is * the objectToken. */ private Hashtable <String , Hashtable<String,objectToken>> objects; /* key = object key; value = a Hashtable whose key is a property name and whose value * id the value of the property for the object. */ private Hashtable<String,Hashtable<String,Object>> properties; /* key = first object key; value = a Hashtable whose key is a role name and whose * value is a Hashtable (key: second object key; value: second object token) */ private Hashtable<String,Hashtable<String,Hashtable<String,objectToken>>> links; /* For use in looking up associations by association name. * key = association name. value = role name at end 1 */ private Hashtable<String,String> end1Role; /* For use in looking up associations by association name. * key = association name. value = role name at end 2 */ private Hashtable<String,String> end2Role; private EPackage classModel; private int keyIndex; private boolean tracing = false; private void trace(String s) {if (tracing) System.out.println(s);} //----------------------------------------------------------------------------------- // constructor //----------------------------------------------------------------------------------- public SimpleObjectGetter(EPackage classModel) { this.classModel = classModel; keyIndex = 0; initialise(); } /** * set the root of the XML instance being read * As there is no XML instance , this has no effect; it is just there to satisfy * the objectGetter interface. * @param el * @throws MapperException */ public void setRoot(Node el) throws MapperException { } /** * initialise this objectGetter to have an empty class model instance */ public void initialise() { objects = new Hashtable<String, Hashtable<String,objectToken>>(); properties = new Hashtable<String,Hashtable<String,Object>>(); links = new Hashtable<String,Hashtable<String,Hashtable<String,objectToken>>>(); end1Role = new Hashtable<String,String>(); end2Role = new Hashtable<String,String>(); } //----------------------------------------------------------------------------------- // methods to build up the class model instance //----------------------------------------------------------------------------------- /** * add a new object to the class model * @param className the class of the object * @return the objectToken for the new object * - to be used to set its properties and associations * @exception MapperException if the class is not in the class model */ public objectToken addObject(String className) throws MapperException { trace("SimpleObjectGetter adding object of class " + className); if (!classIsInModel(className)) throw new MapperException("Class '" + className + "' is not in the class model."); SimpleObjectRep oRep = new SimpleObjectRep(className, nextKey()); Hashtable<String,objectToken> inThisClass = objects.get(className); if (inThisClass == null) inThisClass = new Hashtable<String,objectToken>(); inThisClass.put((String)oRep.objectKey(), oRep); objects.put(className,inThisClass); return oRep; } private String nextKey() { String key = "k_" + keyIndex; keyIndex++; return key; } /** * set the value of a property of an object * @param obj the object whose property is to be set * @param propName the name of the property * @param PropValue the new value of the property * @throws MapperException if the property is not in the class model or the object does not exist. */ public void setProperty(objectToken obj, String propName, Object propValue) throws MapperException { if (!propertyIsInModel(obj.className(),propName)) throw new MapperException("Property '" + propName + "' of class '" + obj.className() + "' is not in the class model."); if (!objectExists(obj)) throw new MapperException("Cannot set property '" + propName + "' because the object does not exist."); if (!(propValue instanceof String)) throw new MapperException("Only String values of property '" + propName + "' are handled"); Hashtable<String,Object> objProps = properties.get(obj.objectKey()); if (objProps == null) objProps = new Hashtable<String,Object>(); objProps.put(propName, propValue); properties.put((String)obj.objectKey(), objProps); } /** * record a link (association instance) between two objects, and the inverse link if it exists * @param obj1 * @param role * @param obj2 * @throws MapperException if the association does not exist. */ public void addLink(objectToken obj1, String role, objectToken obj2) throws MapperException { trace("Adding association "+ obj1.className() + ">" + role + ">" + obj2.className()); if (!associationIsInModel(obj1.className(),role,obj2.className())) throw new MapperException("There is no association '" + role + "' from class '" + obj1.className() + "' to class '" + obj2.className()+ "'"); addAssociation(obj1,role,obj2); String inverseName = MappableAssociation.NON_NAVIGABLE_ROLE_NAME; EReference inverse = inverseAssociation(obj1.className(),role,obj2.className()); if (inverse != null) { addAssociation(obj2, inverse.getName(),obj1); inverseName = inverse.getName(); } /* else throw new MapperException("The association '" + role + "' from class '" + obj1.className() + "' to class '" + obj2.className()+ "' has no inverse."); */ saveAssociationName(role,inverseName); } /* store a link in the Hashtable of links */ private void addAssociation(objectToken obj1, String role, objectToken obj2) { /* key = first object key; value = a Hashtable whose key is a role name and whose * value is a Hashtable (key: second object key; value: second object token) private Hashtable<String,Hashtable<String,Hashtable<String,objectToken>>> links; */ Hashtable<String,Hashtable<String,objectToken>> linksForObject = links.get(obj1.objectKey()); if (linksForObject == null) linksForObject = new Hashtable<String,Hashtable<String,objectToken>>(); Hashtable<String,objectToken> linksForRole = linksForObject.get(role); if (linksForRole == null) linksForRole = new Hashtable<String,objectToken>(); linksForRole.put((String)obj2.objectKey(), obj2); linksForObject.put(role, linksForRole); links.put((String)obj1.objectKey(), linksForObject); } /** * * @param role * @param invRole */ private void saveAssociationName(String role, String invRole) { String assocName = ModelUtil.assocName(role, invRole); end1Role.put(assocName,ModelUtil.end1Role(role, invRole)); end2Role.put(assocName,ModelUtil.end2Role(role, invRole)); } //----------------------------------------------------------------------------------- // methods in the objectGetter interface //----------------------------------------------------------------------------------- /** * return a Vector of objectTokens for all objects in the class which you want written out * in an XML document by OXWriter. * The Vecotr includes objects in subclasses of the named class * These must represent each distinct object only once. * If it cannot return a Vector of unique objectTokens for distinct objects, * or if the source does not represent the class at all, throws a notRepresentedException. * If anything else goes wrong, throw an MDLReadException. */ public Vector<objectToken> getObjects(String className) throws MapperException { if (!classIsInModel(className)) throw new MapperException("Class '" + className + "' is not in the class model."); Vector<objectToken> result = new Vector<objectToken>(); EClass namedClass = getClass(className); for (Iterator<EClass> it = ModelUtil.getAllSubClasses(namedClass).iterator();it.hasNext();) { EClass subClass = it.next(); String subclassName = ModelUtil.getQualifiedClassName(subClass); Hashtable<String,objectToken> classObjects = objects.get(subclassName); if (classObjects != null) for (Enumeration<objectToken> en = classObjects.elements(); en.hasMoreElements();) result.add(en.nextElement()); } return result; } /** * return the value of a property of an object, given its objectToken. * Throw a notRepresentedException if the source does not represent the property. * If anything else goes wrong, throw an MDLReadException. */ public String getPropertyValue(objectToken obj, String propertyName) throws MapperException { String value = ""; if (!propertyIsInModel(obj.className(),propertyName)) throw new MapperException("Property '" + propertyName + "' of class '" + obj.className() + "' is not in the class model."); Hashtable<String,Object> objProps = properties.get(obj.objectKey()); if (objProps == null) return value; Object valObj = objProps.get(propertyName); if (valObj == null) return value; if (!(valObj instanceof String)) throw new MapperException("Only String values of property '" + propertyName + "' are handled"); value = (String)valObj; trace("Returning value '" + value + "' for property " + propertyName); return value; } /** * return a Vector of objectTokens for objects related to this object by some association, * which are all in subclasses of some named class. * If anything goes wrong, throw a MapperException. */ public Vector<objectToken> getAssociatedObjects(objectToken oTok, String relation, String otherClassName, int oneOrTwo) throws MapperException { trace("Follow association " + oTok.className() + " > " + relation + " > " + otherClassName + " end " + oneOrTwo); Vector<objectToken> result = new Vector<objectToken>(); EClass otherClass = getClass(otherClassName); if (otherClass == null) throw new MapperException("Unknown other end class '" + otherClassName + "' for association '" + relation + "'"); /* the relation name will not be recognised if there are no instances of it; * so do not throw an exception. */ String role1 = end1Role.get(relation); String role2 = end2Role.get(relation); trace("Roles: " + role1 + " and " + role2); if ((role1 == null)|(role2 == null)) return result; /* oneOrTwo is defined to be the end of the start object. * The Hashtable links has recorded the role name to get to the other end object. * If the start object is end 1, the other end object is end 2, so we need role2 to get to it. */ String role = role1; if (oneOrTwo == 1) role = role2; /* key = first object key; value = a Hashtable whose key is a role name and whose * value is a Hashtable (key: second object key; value: second object token) private Hashtable<String,Hashtable<String,Hashtable<String,objectToken>>> links; */ Hashtable<String,Hashtable<String,objectToken>> linksForObj = links.get(oTok.objectKey()); if (linksForObj == null) return result; Hashtable<String,objectToken> linksForRole = linksForObj.get(role); if (linksForRole == null) return result; /* get all objects atthe other end of the association which are in * subclasses of the named end class. */ for (Enumeration<objectToken> en = linksForRole.elements();en.hasMoreElements();) { objectToken ot = en.nextElement(); EClass actOtherClass = getClass(ot.className()); if (otherClass.isSuperTypeOf(actOtherClass)) result.add(ot); } trace("results: " + result.size()); return result; } //----------------------------------------------------------------------------------- // checks against the class model //----------------------------------------------------------------------------------- /** * check the cardinalities of every Attribute and Association in the model instance */ public void checkCardinalties() throws MapperException { String errorList = ""; for (Enumeration<String> en = objects.keys();en.hasMoreElements();) { String className = en.nextElement(); // a bad class should have been detected earlier EClass objClass = getClass(className); if (objClass == null) throw new MapperException ("Unexpected class '" + className + "'"); String classPreface = "An object of class '" + className + "' "; // loop over all objects in the class Hashtable<String,objectToken> inClass = objects.get(className); for (Enumeration<objectToken> ep = inClass.elements();ep.hasMoreElements();) { objectToken obj = ep.nextElement(); // check that the object has all mandatory attributes for (Iterator<EAttribute> it = objClass.getEAllAttributes().iterator();it.hasNext();) { EAttribute att = it.next(); if (att.getLowerBound() == 1) { String val = getPropertyValue(obj,att.getName()); if (val.equals("")) errorList = errorList + classPreface + "has no mandatory property '" + att.getName() + "';"; } } /* check the min and max cardinalities of all associations. * These refer to all possible classes at the other end of the association; * so the counts sum over all other end classes. */ for (Iterator<EReference> it = objClass.getEAllReferences().iterator();it.hasNext();) { EReference ref = it.next(); int min = ref.getLowerBound(); int max = ref.getUpperBound(); int actual = 0; /* key = first object key; value = a Hashtable whose key is a role name and whose * value is a Hashtable (key: second object key; value: second object token) private Hashtable<String,Hashtable<String,Hashtable<String,objectToken>>> links; */ Hashtable<String,Hashtable<String,objectToken>> linksForObj = links.get(obj.objectKey()); if (linksForObj != null) { Hashtable<String,objectToken> linksForRole = linksForObj.get(ref.getName()); if (linksForRole != null) actual = linksForRole.size(); } if ((actual ==0) && (min == 1)) errorList = errorList + classPreface + "has a missing mandatory association '" + ref.getName() + "';"; if ((actual > 1) && (max == 1)) errorList = errorList + classPreface + "has cardinality " + actual + " (>1) for association '" + ref.getName() + "';"; } } } if (errorList.length() > 0) throw new MapperException(errorList); } /** * true if the named class is in the class model * FIXME - yet to deal with nested packages */ private boolean classIsInModel(String qualifiedClassName) { return (ModelUtil.getNamedClass(classModel,qualifiedClassName) != null); } /** * * @param className * @return the named EClass */ private EClass getClass(String className) { return ModelUtil.getNamedClass(classModel, className); } /** * true if the named property is in the class model */ private boolean propertyIsInModel(String qualifiedClassName, String propName) { EClass theClass = ModelUtil.getNamedClass(classModel, qualifiedClassName); if (theClass == null) return false; for (Iterator<EAttribute> it = theClass.getEAllAttributes().iterator();it.hasNext();) if (it.next().getName().equals(propName)) return true; return false; } private boolean objectExists(objectToken obj) { Hashtable<String,objectToken> objectsInClass = objects.get(obj.className()); if (objectsInClass == null) return false; return (objectsInClass.get(obj.objectKey()) != null); } /** * @param class1 * @param role * @param class2 * @return true if the named association between the two classes is in the model */ private boolean associationIsInModel(String class1, String role, String class2) { boolean isInModel = false; EClass c1 = ModelUtil.getNamedClass(classModel, class1); EClass c2 = ModelUtil.getNamedClass(classModel, class2); if (c1 == null) return false; if (c2 == null) return false; for (Iterator<EReference> it = c1.getEAllReferences().iterator();it.hasNext();) { EReference er = it.next(); if (er.getName().equals(role)) { EClassifier target = er.getEType(); if ((target instanceof EClass) && (((EClass)target).isSuperTypeOf(c2))) isInModel = true; } } return isInModel; } /** * * @param class1 * @param role * @param class2 * @return the inverse association, if there is one and if the inverse exists */ private EReference inverseAssociation(String class1, String role, String class2) { EReference ref = null; EClass c1 = ModelUtil.getNamedClass(classModel, class1); EClass c2 = ModelUtil.getNamedClass(classModel, class2); if (c1 == null) return null; if (c2 == null) return null; for (Iterator<EReference> it = c1.getEAllReferences().iterator();it.hasNext();) { EReference er = it.next(); if (er.getName().equals(role)) { EClassifier target = er.getEType(); if ((target instanceof EClass) && (((EClass)target).isSuperTypeOf(c2))) ref = er.getEOpposite(); } } return ref; } private boolean checking = true; public XOReader reader() throws MapperException { if (checking) throw new MapperException("SimpleObjectGetter does not implement method reader()"); return null; } }