package er.extensions.eof; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eoaccess.EOModelGroup; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSComparator; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableDictionary; import com.webobjects.foundation.NSSet; import er.extensions.foundation.ERXArrayUtilities; /** * Abstract class defining an ordering of EOEntities that also provides NSComparators to sort entities based on this ordering. * The ordering is based on groups of entities with entities within each group having no defined order. * * <p>This is implemented by creating a dictionary of entity name to group. Group 1 is entities with no dependencies. Group 2 is entities with * dependencies on entities in group 1. Group 3 is entities with dependencies on entities in groups 1 and 2. etc. * The dependencies between entities are determined by the abstract <code>NSDictionary dependenciesByEntity()</code>.</p> * * @author chill */ public abstract class ERXEntityOrder { private static final Logger log = LoggerFactory.getLogger(ERXEntityOrder.class); protected NSMutableDictionary<String, Integer> groupedEntities = new NSMutableDictionary<>(); protected NSArray<EOEntity> allEntities = null; /** * Designated constructor for implementing classes. * * @param modelGroup EOModelGroup to get list of all entities from */ public ERXEntityOrder(EOModelGroup modelGroup) { super(); createListOfEntities(modelGroup); generateOrdering(); } /** * Convenience constructor for implementing classes. Uses <code>EOModelGroup.defaultGroup()</code>. */ public ERXEntityOrder() { this(EOModelGroup.defaultGroup()); } /** * Returns dictionary of group numbers (<code>java.lang.Integer</code>) to entity names. * Group 1 is entities with no dependencies. Group 2 is entities with * dependencies on entities in group 1. Group 3 is entities with * dependencies on entities in groups 1 and 2. etc * * @return dictionary of group numbers to entity names */ public NSMutableDictionary<String, Integer> groupedEntities() { return groupedEntities; } /** * Processes <code>allEntities()</code> and returns a dictionary keyed on * <code>dependencyKeyFor(EOEntity)</code>. The keys are usually <code>entity.name()</code> but are * not required to be. The value associated with each key is an NSSet of the * entity names that have a dependency on the key. This dictionary is used * to determine the dependency ordering. * * @return a dictionary keyed on dependencyKeyFor(EOEntity) */ protected abstract NSDictionary<String, NSSet<String>> dependenciesByEntity(); /** * Calls <code>dependenciesByEntity()</code> to determine dependencies and processes entities * in <code>allEntities()</code> to generate the <code>groupedEntities()</code> dictionary. */ protected void generateOrdering() { NSDictionary<String, NSSet<String>> dependencies = dependenciesByEntity(); NSMutableArray<EOEntity> entities = allEntities().mutableClone(); int groupNum = 1; while (entities.count() > 0) { // Entities that are eligible for this group are NOT added to the master list // immediately to avoid dependencies between entities in the same group NSMutableDictionary<String,Integer> groupDictionary = new NSMutableDictionary<>(); Integer group = Integer.valueOf(groupNum++); log.trace("Building group {}", group); // Examine each entity not already in a group and add it to this group if // all of its dependencies are in previously processed groups. int index = 0; while (index < entities.count()) { EOEntity entity = entities.objectAtIndex(index); log.trace("Processing entity {}", entity.name()); if (hasDependenciesForEntity(dependencies, entity)) { log.trace("Adding entity {} to group {}", entity.name(), group); groupDictionary.setObjectForKey(group, entity.name()); entities.removeObjectAtIndex(index); } else { // This entity still has unresolved dependencies, it will get added to a later group index++; } } // If an error is found, log out information to make debugging easier if (groupDictionary.count() == 0) { log.error("Stopping, circular relationships found for {}", entities.valueForKey("name")); NSSet<String> remainingEntities = new NSSet<>((NSArray<String>)entities.valueForKey("name")); for (int i = 0; i < entities.count(); i++) { EOEntity entity = entities.objectAtIndex(i); NSSet<String> remainingDependencies = dependentEntities(dependencies, entity).setByIntersectingSet(remainingEntities); log.error("{} has dependencies on {}", entity.name(), remainingDependencies); } throw new RuntimeException("Circular relationships found for " + entities.valueForKey("name")); } groupedEntities().addEntriesFromDictionary(groupDictionary); } if (log.isTraceEnabled()) { log.trace("Entity groups in dependency order:"); for (int i = 1; i < groupNum; i++) { Integer iVal = Integer.valueOf(i); log.trace("Group {}: {}", iVal, groupedEntities().allKeysForObject(iVal)); log.trace(""); } } } /** * @param dependencies dictionary from <code>dependenciesByEntity()</code> * @param entity entity to check for dependencies * * @return true if <code>groupedEntities()</code> has all the entities named in <code>dependentEntities(dependencies, entity)</code>. */ protected boolean hasDependenciesForEntity(NSDictionary<String, NSSet<String>> dependencies, EOEntity entity) { // Abstract entities etc may not have an entry if (dependentEntities(dependencies, entity) == null) { return true; } for (String entityName : dependentEntities(dependencies, entity)) { if (groupedEntities().objectForKey(entityName) == null) { return false; } } return true; } /** * @param dependencies result from <code>dependenciesByEntity()</code> * @param entity EOEntity to return dependencies set for * * @return set of names of entities that are dependent on entity */ protected NSSet<String> dependentEntities(NSDictionary<String, NSSet<String>> dependencies, EOEntity entity) { return dependencies.objectForKey(dependencyKeyFor(entity)); } /** * This implementation returns <code>entity.name()</code>. * @param entity EOEntity to return key into dependency dictionary for * * @return key for <code>entity</code> into dependency dictionary returned by <code>dependenciesByEntity()</code> */ protected String dependencyKeyFor(EOEntity entity) { return entity.name(); } /** * Creates list of all entities (excluding prototype entities) in all models in <code>modelGroup</code>. * * @param modelGroup EOModelGroup to get list of all entities from */ public void createListOfEntities(EOModelGroup modelGroup) { NSArray<NSArray<EOEntity>> arrayOfArrayOfEntites = (NSArray<NSArray<EOEntity>>) modelGroup.models().valueForKey("entities"); NSArray<EOEntity> entities = ERXArrayUtilities.flatten(arrayOfArrayOfEntites); NSMutableArray<EOEntity> filteredEntities = new NSMutableArray(entities.count()); for (EOEntity entity : entities) { if ( ! ERXModelGroup.isPrototypeEntity(entity)) { filteredEntities.addObject(entity); } } allEntities = filteredEntities.immutableClone(); } /** * @return list of all entities in all models in the model group */ public NSArray<EOEntity> allEntities() { return allEntities; } /** * NSComparator to sort on the ascending EOEntity group number from ordering.entityOrdering(). * This produces an ordering suitable for deleting data. */ public static class EntityDeleteOrderComparator extends NSComparator { protected ERXEntityOrder eRXEntityOrder; public EntityDeleteOrderComparator(ERXEntityOrder ordering) { super(); eRXEntityOrder = ordering; } @Override public int compare(Object object1, Object object2) throws NSComparator.ComparisonException { EOEntity entity1 = (EOEntity) object1; EOEntity entity2 = (EOEntity) object2; Number group1 = eRXEntityOrder.groupedEntities().objectForKey(entity1.name()); Number group2 = eRXEntityOrder.groupedEntities().objectForKey(entity2.name()); return NSComparator.AscendingNumberComparator.compare(group1, group2); } } /** * NSComparator to sort on the descending EOEntity group number from ordering.entityOrdering(). * This produces an ordering suitable for inserting data. */ public static class EntityInsertOrderComparator extends NSComparator { protected ERXEntityOrder eRXEntityOrder; public EntityInsertOrderComparator(ERXEntityOrder ordering) { super(); eRXEntityOrder = ordering; } @Override public int compare(Object object1, Object object2) throws NSComparator.ComparisonException { EOEntity entity1 = (EOEntity) object1; EOEntity entity2 = (EOEntity) object2; Number group1 = eRXEntityOrder.groupedEntities().objectForKey(entity1.name()); Number group2 = eRXEntityOrder.groupedEntities().objectForKey(entity2.name()); return NSComparator.DescendingNumberComparator.compare(group1, group2); } } }