diff --git a/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXCopyable.java b/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXCopyable.java new file mode 100644 index 00000000000..d47d7b6b904 --- /dev/null +++ b/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXCopyable.java @@ -0,0 +1,1493 @@ +package er.extensions.eof; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.webobjects.eoaccess.EOAttribute; +import com.webobjects.eoaccess.EOEntity; +import com.webobjects.eoaccess.EOModel; +import com.webobjects.eoaccess.EOProperty; +import com.webobjects.eoaccess.EORelationship; +import com.webobjects.eoaccess.EOUtilities; +import com.webobjects.eocontrol.EOEditingContext; +import com.webobjects.eocontrol.EOEnterpriseObject; +import com.webobjects.eocontrol.EOGlobalID; +import com.webobjects.foundation.NSArray; +import com.webobjects.foundation.NSDictionary; +import com.webobjects.foundation.NSMutableArray; +import com.webobjects.foundation.NSMutableDictionary; +import com.webobjects.foundation.NSMutableSet; +import com.webobjects.foundation.NSTimestamp; + +import er.extensions.foundation.ERXArrayUtilities; + +/** + * This class specifies an interface for flexible copying of + * {@code EOEnterpriseObject}s, a default implementation for doing the actual + * copying, and a Utility class that provides convenience methods to make + * implementing the interface easier. + *

+ * There are several ways to implement this interface: + *

    + *
  1. Manually implement the interface in each {@code Entity.java} class that + * you want to be able to copy
    This is the quickest way to get started. + * Simply add specify that your EO implements ERXCopyable<MyEntity> in the + * class declaration, then implement the required methods. Here is what you'll + * need to add to get started: + * + *
    + * @Override
    + * public MyEntity copy() {
    + * 	MyEntity copy = copy(new NSMutableDictionary<EOGlobalID, ERXCopyable<?>>());
    + * 	return copy;
    + * }
    + * 
    + * @Override
    + * public MyEntity copy(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> copiedObjects) {
    + * 	MyEntity copy = ERXCopyable.DefaultImplementation.copy(copiedObjects, (MyEntity) this);
    + * 	return copy;
    + * }
    + * 
    + * @Override
    + * public MyEntity duplicate(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> copiedObjects) {
    + * 	MyEntity duplicate = ERXCopyable.Utility.deepCopy(copiedObjects, (MyEntity) this);
    + * 	return duplicate;
    + * }
    + * 
    + * + *
  2. + *
  3. Manually implement the interface in a {@link ERXGenericRecord} subclass + *
    This is almost as easy as option #1 but has the added advantage of + * allowing you to implement the interface just once, and then override the + * default behavior as needed for individual EOs. Simply specify that your + * {@link ERXGenericRecord} subclass implements + * ERXCopyable<MyGenericRecord> in the class declaration and then + * implement the required methods. The defaults are very similar to option #1: + * + *
    + * @Override
    + * public MyGenericRecord copy() {
    + * 	MyGenericRecord copy = copy(new NSMutableDictionary<EOGlobalID, ERXCopyable<?>>());
    + * 	return copy;
    + * }
    + * 
    + * @Override
    + * public MyGenericRecord copy(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> copiedObjects) {
    + * 	MyGenericRecord copy = ERXCopyable.DefaultImplementation.copy(copiedObjects, this);
    + * 	return copy;
    + * }
    + * 
    + * @Override
    + * public MyGenericRecord duplicate(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> copiedObjects) {
    + * 	MyGenericRecord duplicate = ERXCopyable.Utility.deepCopy(copiedObjects, this);
    + * 	return duplicate;
    + * }
    + * 
    + * + *
  4. + *
  5. Add {@code UserInfo} dictionary entries into your EOModel and make some + * additions to your EOGenerator templates that will automatically implement + * this interface based on your model settings.
    This option is the most + * powerful and flexible, and requires the least amount of ongoing programming, + * but takes more work to get setup. It's well worth it if you are going to be + * implementing ERXCopyable on more than just a few EOs. Here's what you'll need + * to do:
  6. + *
      + *
    1. Create or modify your own EOGenerator template with the following + * additions:
    2. + * + *
    + *
+ * + * @param + * the specific subclass of {@code ERXCopyable} that is being copied + * + * @author Chuck Hill + * @author Sacha Mallais + * @author David Avendasora + */ +public interface ERXCopyable extends ERXEnterpriseObject { + + /** + *

+ * {@link Enum} that specifies the valid ways in which an {@link EOModel}'s + * {@link EOProperty}s can be copied. + *

+ *

+ * The {@link CopyType} is specified for a given {@link EOProperty} using a + * String entry in the property's UserInfo dictionary with a key of + * {@code ERXCopyable.CopyType} and a value matching (case-insensitively) + * one of the elements of this enum. + *

+ * + * @author David Avendasora + */ + public enum CopyType { + + /** + * For attributes and relationships.Stored as " + * {@code ERXCopyable.CopyType = Nullify;}" in the property's UserInfo + * dictionary. This setting does not copy the {@code original}'s + * value. It sets the {@code copy}'s value to null. + */ + NULLIFY("Nullify"), + + /** + * For attributes and relationships. Stored as " + * {@code ERXCopyable.CopyType = Reference;}" in the {@link EOAttribute} + * 's and {@link EORelationship}'s UserInfo dictionary. For attributes + * this simply sets the same value on the destination as the source. For + * relationships, this sets the {@code copy}'s relationship to point to + * the same {@link ERXCopyable} object as the {@code original}'s + * relationship does. WARNING: if you use this on a + * non-flattening, to-many relationship, the destination objects will be + * moved from the {@code original} to the {@code copy}. + */ + REFERENCE("Reference"), + + /** + * For relationships only. Stored as " + * {@code ERXCopyable.CopyType = Shallow;}" in the + * {@link EORelationship}'s UserInfo dictionary. New instances of the + * destination {@link ERXCopyable} objects will be made and all of the + * original's attributes and relationships will be reference copied. + */ + SHALLOW("Shallow"), + + /** + * For relationships only. Stored as " + * {@code ERXCopyable.CopyType = Deep;}" in the {@link EORelationship}'s + * UserInfo dictionary. Duplicates each of the destination + * {@link ERXCopyable} objects using their implementation of the + * {@link #duplicate(NSMutableDictionary)} method. + */ + DEEP("Deep"), + + /** + * For attributes only. Stored as " + * {@code ERXCopyable.CopyType = CurrentTimestamp;}" in the + * {@link EOAttribute}'s UserInfo dictionary. This setting does + * not copy the {@code original}'s value. It sets the {@code copy}'s + * value to the current date and time using + * {@code new NSTimestamp()} + */ + CURRENT_TIMESTAMP("CurrentTimestamp"); + + private final String _type; + + CopyType(String type) { + _type = type; + } + + public String type() { + return _type; + } + + /** + * @param typeAsString + * a String to match (case-insensitive) to a {@link CopyType} + * + * @return the {@link CopyType} equivalent to the {@code typeAsString} + * + * @author David Avendasora + */ + public static CopyType get(String typeAsString) { + CopyType copyType = null; + if (typeAsString != null) { + for (CopyType ct : CopyType.values()) { + if (typeAsString.equalsIgnoreCase(ct.type())) { + copyType = ct; + break; + } + } + } + return copyType; + } + } + + /** + *

+ * This class provides a default implementation of ERXCopyable that handles + * the most common situations encountered copying {@link EOEnterpriseObject} + * s. + *

+ *

+ * Notes + *

    + *
  • Debugging information can be turned on with the DEBUG level of the + * log4j logger + * + *
    +	 * er.extensions.eof.ERXCopyable}.
    +	 * 
    + * + *
  • + *
  • If you implement your own deep copy of relationships you should + * register the new object before copying its relationships to so that + * circular relationships will be copied correctly. For example: + * + *
    +	 * EOGlobalID globalID = editingContext().globalIDForObject(this);
    +	 * copiedObjects.setObjectForKey(copy, globalID);
    +	 * 
    + * + *
  • + *
+ *

+ */ + public static class DefaultImplementation { + + /** + * Returns a copy of this object. The actual copy mechanism, + * {@link CopyType#REFERENCE Reference}, {@link CopyType#DEEP Deep}, + * etc. is up to the object being copied. If a copy already exists in + * the copiedObjects dictionary, then that existing copy is + * returned instead of making a new copy. This allows complex graphs of + * objects, including those with cycles, to be copied without producing + * duplicate objects. + * + * @param + * the Type of the {@code source} + * + * @param copiedObjects + * the copied objects keyed on the EOGlobalID of the object + * the copy was made from. + * @param source + * the {@code ERXCopyable} to copy + * @return a copy of this object + */ + @SuppressWarnings("unchecked") + public static T copy(NSMutableDictionary> copiedObjects, T source) { + EOGlobalID globalID = source.editingContext().globalIDForObject(source); + ERXCopyable.copyLogger.debug("Copying object " + source.userPresentableDescription()); + T copy = (T) copiedObjects.objectForKey(globalID); + if (copy == null) { + ERXCopyable.copyLogger.debug("Creating duplicate."); + copy = (T) source.duplicate(copiedObjects); + copiedObjects.setObjectForKey(copy, globalID); + } + else { + ERXCopyable.copyLogger.debug("A duplicate was already made. Using the existing duplicate instead of creating a new one."); + } + return copy; + + } + + /** + * Returns a deep copy of this object. + * + * @param + * the Type of the {@code source} + * + * @param copiedObjects + * the copied objects keyed on the EOGlobalID of the object + * the copy was made from. + * @param source + * @return a deep copy of this object + */ + public static T duplicate(NSMutableDictionary> copiedObjects, T source) { + T duplicate = Utility.deepCopy(copiedObjects, source); + return duplicate; + } + + } + + /** + * This class provides utility methods for use implementing ERXCopyable. + * They handle the most common situations encountered copying EO objects. + * The {@link DefaultImplementation} uses them internally. The + * implementations of + *
    + *
  • {@link Utility#modelCopy(NSMutableDictionary, ERXCopyable)}
  • + *
  • {@link Utility#referenceCopy(ERXCopyable)}
  • + *
  • {@link Utility#shallowCopy(ERXCopyable)}
  • + *
  • {@link Utility#deepCopy(NSMutableDictionary, ERXCopyable)}
  • + *
+ * should be suitable for most {@link EOEnterpriseObject} instances to use + * for their {@link ERXCopyable#duplicate(NSMutableDictionary)} method. + * However there are some situations that can not be handled with this + * generic code:
+ *
    + *
  1. An attribute or relationship must not be copied (e.g. order numbers). + *
  2. + *
  3. An attribute or relationship needs special handling (e.g. + * dateModified should reflect when the copy was made, not when the original + * object was created).
  4. + *
  5. An EO object should not be copied the same way in all situations + * (e.g. the relationship from one object should be copied deeply, but from + * another object should be a reference copy).
  6. + *
  7. The relationships must be copied in a certain order (e.g. due to side + * effects in the methods setting the relationships).
  8. + *
+ * In these situations you will need to write a custom implementation of the + * duplicate(NSMutableDictionary) method. This can be as simple as invoking + * the default implementation and then cleaning up the result to as complex + * as doing it all by hand. {@link Utility} also provides lower-level + * methods that you can use to copy any or all attributes or relationships + * when creating a custom duplicate(NSMutableDictionary) method. + * + *

+ * Debugging information can be turned on with the DEBUG level of the log4j + * logger er.extensions.eof.ERXCopyable. + *

+ */ + public static class Utility { + + protected static volatile NSMutableDictionary> _exposedPKAndFKAttributeDictionary = null; + protected static volatile NSMutableDictionary> _classAttributesDictionary = null; + protected static volatile NSMutableDictionary> _classRelationshipsDictionary = null; + + /** + * Returns the entity for the current object. Defers to + * {@link ERXEOAccessUtilities#entityNamed(EOEditingContext, String) + * ERXEOAccessUtilities.entityNamed()} for the actual work. + * + * @param + * the Type of the {@code enterpriseObject} + * @param enterpriseObject + * + * @return {@link EOEntity} of the {@code enterpriseObject} + */ + public static EOEntity entity(T enterpriseObject) { + EOEditingContext editingContext = enterpriseObject.editingContext(); + String entityName = enterpriseObject.entityName(); + EOEntity entity = ERXEOAccessUtilities.entityNamed(editingContext, entityName); + return entity; + } + + /** + * When an EO object is created it can already have some relationships + * set. This can come from to one relationships that are marked as 'owns + * destination' and also from the effects of awakeFromInsertion() and + * need some special handling prior to making a copy. + *
    + *
  1. All objects are disconnected from the relationship.
  2. + *
  3. If a disconnected object has a temporary EOGlobalID it is + * deleted.
  4. + *
+ * + * @param + * the Type of the {@code source} and {@code destination} + * @param source + * the {@code ERXCopyable} that copy was created from + * @param destination + * the newly instantiated copy of source that needs to have + * its relationships cleaned + */ + public static void cleanRelationships(T source, T destination) { + ERXCopyable.copyLogger.debug("Cleaning related objects in copy of " + source); + EOEditingContext editingContext = source.editingContext(); + EOEntity entity = Utility.entity(source); + + // To-Many relationships + for (String relationshipName : destination.toManyRelationshipKeys()) { + @SuppressWarnings("unchecked") + NSArray relatedObjects = (NSArray) destination.valueForKey(relationshipName); + + if (relatedObjects.count() > 0) { + entity.relationshipNamed(relationshipName); + ERXCopyable.copyLogger.debug("Removing objects in to-many relationship " + relationshipName); + for (ERXCopyable relatedObject : relatedObjects) { + + destination.removeObjectFromBothSidesOfRelationshipWithKey(relatedObject, relationshipName); + if (relatedObject.isNewObject()) { + editingContext.deleteObject(relatedObject); + } + } + } + } + + // To-one relationships + for (String relationshipName : destination.toOneRelationshipKeys()) { + ERXCopyable relatedObject = (ERXCopyable) destination.valueForKey(relationshipName); + if (relatedObject != null) { + ERXCopyable.copyLogger.debug("Removing object in to-one relationship " + relationshipName); + destination.removeObjectFromBothSidesOfRelationshipWithKey(relatedObject, relationshipName); + + if (Utility.globalIDForObject(relatedObject).isTemporary()) { + source.editingContext().deleteObject(relatedObject); + } + } + } + } + + /** + * This copies only the class attributes from the source + * {@code ERXCopyable} to the destination. However if an attribute is a + * class property and also used in a relationship it is assumed to be an + * exposed primary- or foreign-key and are not copied. Such + * attributes are set to null. See + * {@link #exposedPKAndFKAttributes(ERXCopyable)} for details on how + * this is determined. It can be used when creating custom + * implementations of the + * {@link ERXCopyable#duplicate(NSMutableDictionary)} method. + * + * @param + * the Type of the {@code source} and {@code destination} + * @param source + * object to copy attribute values from + * @param destination + * object to copy attribute values to + */ + public static void copyClassAttributes(T source, T destination) { + EOEntity entity = Utility.entity(source); + ERXCopyable.copyLogger.debug("Copying all attributes for " + source.userPresentableDescription()); + NSArray attributes = Utility.classAttributes(entity); + for (EOAttribute attribute : attributes) { + Utility.copyAttribute(source, destination, attribute); + } + } + + /** + * @param + * the Type of the {@code source} and {@code destination} + * @param source + * object to copy the attribute value from + * @param destination + * object to copy the attribute value to + * @param attribute + * the {@link EOAttribute} that should have its value copied + * @since Feb 10, 2013 + */ + public static void copyAttribute(T source, T destination, EOAttribute attribute) { + String attributeName = attribute.name(); + Object sourceValue = source.storedValueForKey(attributeName); + NSArray exposedPKAndFKAttributes = Utility.exposedPKAndFKAttributes(source); + if (exposedPKAndFKAttributes.containsObject(attribute)) { + ERXCopyable.copyLogger.debug("Nulling exposed key " + attributeName); + destination.takeStoredValueForKey(null, attributeName); + } + else { + ERXCopyable.copyLogger.debug("Copying attribute " + attributeName + ", value " + sourceValue); + destination.takeStoredValueForKey(sourceValue, attributeName); + } + } + + /** + * Returns a deep copy of this object, the attribute values are + * reference copied and the relationships are copied by calling + * {@link ERXCopyable#copy(NSMutableDictionary)} on them. Thus each + * related object will be copied based on its own implementation of the + * {@link ERXCopyable#duplicate(NSMutableDictionary)} method. The copy + * is registered with {@code copiedObjects} dictionary as soon as it is + * created so that circular relationships can be safely copied without + * triggering infinite loops. This method of copying is suitable for + * duplicating complex graphs of objects. + * + * @param + * the Type of the {@code source} object + * @param copiedObjects + * the dictionary of objects that have already been copied, + * keyed on the {@link EOGlobalID}s of the {@code source} + * object that the copy was made from. + * @param source + * the subclass of {@code ERXCopyable} to copy + * @return a copy of this object + */ + public static T deepCopy(NSMutableDictionary> copiedObjects, T source) { + + ERXCopyable.copyLogger.debug("Making deep copy of " + source.userPresentableDescription()); + + T copy = Utility.newInstance(source); + + // Register this object right away to handle circular relationships + copiedObjects.setObjectForKey(copy, Utility.globalIDForObject(source)); + + Utility.copyClassAttributes(source, copy); + Utility.deepCopyClassRelationships(copiedObjects, source, copy); + + return copy; + + } + + /** + * This copies related objects from the source {@code ERXCopyable} to + * the destination by calling deepCopyRelationship on them. It can be + * used when creating custom implementations of the duplicate() method + * in ERXCopyable. Only relationships which are class properties are + * copied. + * + * @param + * the Type of the {@code source} and {@code destination} + * + * @param copiedObjects + * the copied objects keyed on the EOGlobalID of the object + * the copy was made from + * @param source + * the subclass of {@code ERXCopyable} to copy attribute + * values from + * @param destination + * the subclass of {@code ERXCopyable} to copy attribute + * values to + */ + public static void deepCopyClassRelationships(NSMutableDictionary> copiedObjects, T source, T destination) { + ERXCopyable.copyLogger.debug("Deep copying relationships for " + source.userPresentableDescription()); + EOEntity entity = Utility.entity(source); + NSArray relationships = Utility.classRelationships(entity); + for (EORelationship relationship : relationships) { + Utility.deepCopyRelationship(copiedObjects, source, destination, relationship); + } + } + + /** + * @param entity + * the {@link EOEntity} for the object being copied + * @return an array of {@link EOAttribute}s that are designated as class + * attributes in the {@code entity} + */ + public static synchronized NSArray classRelationships(EOEntity entity) { + String entityName = entity.name(); + if (Utility._classRelationshipsDictionary == null) { + Utility._classRelationshipsDictionary = new NSMutableDictionary>(); + } + NSArray classRelationships = Utility._classRelationshipsDictionary.objectForKey(entityName); + if (classRelationships == null) { + NSArray allRelationships = entity.relationships(); + NSMutableArray relationships = new NSMutableArray(); + for (EORelationship relationship : allRelationships) { + if (entity.classProperties().containsObject(relationship)) { + relationships.addObject(relationship); + } + } + classRelationships = relationships.immutableClone(); + Utility._classRelationshipsDictionary.setObjectForKey(classRelationships, entityName); + } + return classRelationships; + } + + /** + * This copies the object(s) for the named relationship from the source + * {@code ERXCopyable} to the destination by calling + * {@link ERXCopyable#copy(NSMutableDictionary)} on them. Thus each + * related object will be copied by its own reference, shallow, deep, or + * custom {@link ERXCopyable#duplicate(NSMutableDictionary)} method. It + * can be used when creating custom implementations of the + * {@link ERXCopyable#duplicate(NSMutableDictionary)} method. + * + * @param + * the Type of the {@code source} and {@code destination} + * @param copiedObjects + * the copied objects keyed on the {@code EOGlobalID} of the + * object the copy was made from + * @param source + * the subclass of {@code ERXCopyable} to copy attribute + * values from + * @param destination + * the subclass of {@code ERXCopyable} to copy attribute + * values to + * @param relationship + * the {@link EORelationship} to copy + */ + public static void deepCopyRelationship(NSMutableDictionary> copiedObjects, T source, T destination, EORelationship relationship) { + if (relationship.isToMany()) { + Utility.deepCopyToManyRelationship(copiedObjects, source, destination, relationship); + } + else { + Utility.deepCopyToOneRelationship(copiedObjects, source, destination, relationship); + } + } + + /** + * @param + * the Type of the {@code source} and {@code destination} + * @param copiedObjects + * the copied objects keyed on the {@code EOGlobalID} of the + * object the copy was made from + * @param source + * the subclass of {@code ERXCopyable} to copy attribute + * values from + * @param destination + * the subclass of {@code ERXCopyable} to copy attribute + * values to + * @param relationship + * the to-one {@link EORelationship} to copy + */ + public static void deepCopyToOneRelationship(NSMutableDictionary> copiedObjects, T source, T destination, EORelationship relationship) { + String relationshipName = relationship.name(); + EOEntity sourceEntity = relationship.entity(); + String sourceEntityName = sourceEntity.name(); + ERXCopyable original = (ERXCopyable) source.valueForKey(relationshipName); + if (original != null) { + ERXCopyable.copyLogger.debug("Copying to-one relationship " + sourceEntityName + "." + relationshipName); + ERXCopyable.copyLogger.debug(" from " + source); + ERXCopyable.copyLogger.debug("Copying " + original.userPresentableDescription()); + ERXCopyable copy = original.copy(copiedObjects); + destination.addObjectToBothSidesOfRelationshipWithKey(copy, relationshipName); + } + } + + /** + * @param + * the Type of the {@code source} and {@code destination} + * @param copiedObjects + * the copied objects keyed on the {@code EOGlobalID} of the + * object the copy was made from + * @param source + * the subclass of {@code ERXCopyable} to copy attribute + * values from + * @param destination + * the subclass of {@code ERXCopyable} to copy attribute + * values to + * @param relationship + * the to-many {@link EORelationship} to copy + */ + public static void deepCopyToManyRelationship(NSMutableDictionary> copiedObjects, T source, T destination, EORelationship relationship) { + String relationshipName = relationship.name(); + String inverseRelationshipName = null; + if (relationship.inverseRelationship() != null) { + inverseRelationshipName = relationship.inverseRelationship().name(); + } + ERXCopyable.copyLogger.debug("Copying to-many relationship " + relationshipName); + ERXCopyable.copyLogger.debug(" from " + source); + + @SuppressWarnings("unchecked") + NSArray originals = ((NSArray) source.valueForKey(relationshipName)).immutableClone(); + @SuppressWarnings("unchecked") + NSArray destinationInitialRelatedObjects = ((NSArray) destination.valueForKey(relationshipName)).immutableClone(); + ERXCopyable.copyLogger.debug("Copying " + originals.count() + " object(s) for relationship " + relationshipName); + + for (T original : originals) { + T copy = (T) original.copy(copiedObjects); + + /* + * This is a tricky part. Making the copy in the previous line + * may have already added objects to the relationship that we + * are about to set. We need to check for this so that we do not + * create duplicated relationships. + */ + if (!destinationInitialRelatedObjects.containsObject(copy)) { + ERXCopyable.copyLogger.debug("Adding " + copy.userPresentableDescription() + " to " + relationshipName + " of " + destination.userPresentableDescription()); + if (inverseRelationshipName == null) { + destination.addObjectToBothSidesOfRelationshipWithKey(copy, relationshipName); + } + else { + copy.addObjectToBothSidesOfRelationshipWithKey(destination, inverseRelationshipName); + } + } + } + } + + /** + * Returns an array of attribute names from the {@link EOEntity} of + * {@code source} that are used in the primary key, or in forming + * relationships. These can be presumed to be exposed primary or foreign + * keys and must be handled differently when copying an object. + * + * @param + * the Type of the {@code source} object + * @param source + * the {@code ERXCopyable} to copy attribute values from + * @return an array of {@link EOAttribute#name()} values from the + * {@code source}'s {@link EOEntity} that are used in forming + * {@link EORelationship}s. + **/ + public static NSArray exposedPKandFKAttributeNames(T source) { + @SuppressWarnings("unchecked") + NSArray attributeNames = (NSArray) exposedPKAndFKAttributes(source).valueForKey("name"); + return attributeNames; + } + + /** + * Returns an array of class {@link EOAttribute}s from the + * {@link EOEntity} of {@code source} that are used in the primary key, + * or in forming {@link EORelationship}s. These are presumed to be + * exposed primary- or foreign-keys and must be handled differently when + * copying an object. + * + * @param + * the Type of the {@code source} object + * @param source + * the subclass of {@code ERXCopyable} that will be copied + * @return an array of attribute names from the {@code EOEntity} of + * source that are used in forming relationships. + **/ + public static synchronized NSArray exposedPKAndFKAttributes(T source) { + EOEntity entity = Utility.entity(source); + String entityName = entity.name(); + if (Utility._exposedPKAndFKAttributeDictionary == null) { + Utility._exposedPKAndFKAttributeDictionary = new NSMutableDictionary>(); + } + NSArray exposedPKAndFKAttributes = Utility._exposedPKAndFKAttributeDictionary.objectForKey(entityName); + if (exposedPKAndFKAttributes == null) { + ERXCopyable.copyLogger.debug("Checking " + entityName + " for Primary and/or Foreign Key attributes that are marked as class properties in the EOModel..."); + NSArray classAttributes = classAttributes(entity); + NSArray primaryAndForeignKeyAttributes = primaryAndForeignKeyAttributes(source); + exposedPKAndFKAttributes = ERXArrayUtilities.intersectingElements(classAttributes, primaryAndForeignKeyAttributes); + if (exposedPKAndFKAttributes.isEmpty()) { + ERXCopyable.copyLogger.debug("--> NO Primary or Foreign Key attributes are marked as class properties in the EOModel. Excellent! Good work designer."); + } + else { + ERXCopyable.copyLogger.debug("--> The following Primary and/or Foreign Key attributes are marked as class properties in the EOModel." + exposedPKAndFKAttributes.componentsJoinedByString(",") + ". There better be a good reason."); + } + Utility._exposedPKAndFKAttributeDictionary.setObjectForKey(exposedPKAndFKAttributes, entityName); + } + return exposedPKAndFKAttributes; + } + + /** + * @param entity + * the {@link EOEntity} for the object being copied + * @return an array of {@link EOAttribute}s that are designated as class + * attributes in the {@code entity} + */ + public static synchronized NSArray classAttributes(EOEntity entity) { + String entityName = entity.name(); + if (Utility._classAttributesDictionary == null) { + Utility._classAttributesDictionary = new NSMutableDictionary>(); + } + NSArray classAttributes = Utility._classAttributesDictionary.objectForKey(entityName); + if (classAttributes == null) { + NSArray allAttributes = entity.attributes(); + NSMutableArray attributes = new NSMutableArray(); + for (EOAttribute attribute : allAttributes) { + if (entity.classProperties().containsObject(attribute)) { + attributes.addObject(attribute); + } + } + classAttributes = attributes.immutableClone(); + Utility._classAttributesDictionary.setObjectForKey(classAttributes, entityName); + } + return classAttributes; + } + + /** + * Convenience method to get {@link EOGlobalID} for an + * {@link EOEnterpriseObject} from its own {@link EOEditingContext}. + * + * @param enterpriseObject + * the {@code EOEnterpriseObject} to return the EOGlobalID + * for + * @return the {@code EOGlobalID} of the {@code enterpriseObject} + * parameter + */ + public static EOGlobalID globalIDForObject(ERXCopyable enterpriseObject) { + EOGlobalID globalID = enterpriseObject.editingContext().globalIDForObject(enterpriseObject); + return globalID; + } + + /** + * This creates and returns a new instance of the same Entity as source. + * When an EO object is created it can already have some relationships + * and attributes set. These can come from to one relationships that are + * marked as 'owns destination' and also from the effects of + * awakeFromInsertion(). Preset attributes should be overwritten when + * all attributes are copied, but the relationships need some special + * handling. See the method + * {@link Utility#cleanRelationships(ERXCopyable, ERXCopyable)} for + * details on what is done. This method can be used when creating custom + * implementations of the + * {@link ERXCopyable#duplicate(NSMutableDictionary)} method. + * + * @param + * the Type of the {@code source} + * + * @param source + * the subclass of {@code ERXCopyable} to copy + * @return a new instance of the same Entity as source + */ + public static T newInstance(T source) { + // ** require [valid_source] source != null; **/ + ERXCopyable.copyLogger.debug("Making new instance of " + source.userPresentableDescription()); + @SuppressWarnings("unchecked") + T destination = (T) EOUtilities.createAndInsertInstance(source.editingContext(), source.entityName()); + Utility.cleanRelationships(source, destination); + return destination; + } + + /** + * Returns a copy of this object by reference. This is equivalent to + * return this; on an {@code ERXCopyable}. This method of + * copying is suitable for lookup list items and other objects which + * should never be duplicated. + * + * @param + * the Type of the {@code source} + * @param source + * the subclass of {@code ERXCopyable} to copy + * @return a copy of this object + */ + public static T referenceCopy(T source) { + ERXCopyable.copyLogger.debug("Reference copying " + source); + return source; + } + + /** + * Returns a shallow copy of this object, the attribute and + * relationships are copied by reference. This method of copying is + * suitable for things like an order item where duplication of the + * product is not wanted and where the order will not be changed (the + * copied order item will be on the original order, not a copy of it). + * + * @param + * the Type of the {@code source} + * @param source + * the subclass of {@code ERXCopyable} to copy + * @return a copy of this object + */ + public static T shallowCopy(T source) { + ERXCopyable.copyLogger.debug("Making shallow copy of " + source); + T copy = Utility.newInstance(source); + Utility.copyClassAttributes(source, copy); + Utility.referenceCopyClassRelationships(source, copy); + return copy; + } + + /** + * This copies related objects from the source {@code ERXCopyable} to + * the destination by reference. Only relationships which are class + * properties are copied. It can be used when creating custom + * implementations of the duplicate() method in ERXCopyable. + * + * @param + * the Type of the {@code source} and {@code destination} + * @param source + * the subclass of {@code ERXCopyable} to copy attribute + * values from + * @param destination + * the subclass of {@code ERXCopyable} to copy attribute + * values to + */ + public static void referenceCopyClassRelationships(T source, T destination) { + ERXCopyable.copyLogger.debug("Reference copying relationships for " + source); + EOEntity entity = EOUtilities.entityForObject(source.editingContext(), source); + for (EORelationship relationship : classRelationships(entity)) { + if (entity.classProperties().containsObject(relationship)) { + Utility.referenceCopyRelationship(source, destination, relationship); + } + } + } + + /** + * @param + * the Type of the {@code source} and {@code destination} + * @param source + * the subclass of {@code ERXCopyable} to copy attribute + * values from + * @param destination + * the subclass of {@code ERXCopyable} to copy attribute + * values to + * @param relationship + * the {@link EORelationship} to copy from the {@code source} + * to the {@code destination} + */ + public static void referenceCopyRelationship(T source, T destination, EORelationship relationship) { + if (relationship.isToMany()) { + Utility.referenceCopyToManyRelationship(source, destination, relationship); + } + else { + Utility.referenceCopyToOneRelationship(source, destination, relationship); + } + + } + + /** + * This copies related objects from the source {@code ERXCopyable} to + * the destination by reference. Only relationships which are class + * properties are copied. It can be used to streamline creating custom + * implementations of the + * {@link ERXCopyable#duplicate(NSMutableDictionary)} method. + * + * @param + * the Type of the {@code source} and {@code destination} + * @param source + * the subclass of {@code ERXCopyable} to copy to-one + * relationships values from + * @param destination + * the subclass of {@code ERXCopyable} to copy to-one + * relationships values to + */ + public static void referenceCopyToOneClassRelationships(T source, T destination) { + ERXCopyable.copyLogger.debug("Reference copying all to-one relationships for " + source.userPresentableDescription()); + EOEntity entity = Utility.entity(source); + for (EORelationship relationship : classRelationships(entity)) { + boolean isClassProperty = entity.classProperties().containsObject(relationship); + if (!relationship.isToMany() && isClassProperty) { + Utility.referenceCopyToOneRelationship(source, destination, relationship); + } + } + } + + /** + * @param + * the Type of the {@code source} and {@code destination} + * @param source + * the subclass of {@code ERXCopyable} to copy the + * relationship's value from + * @param destination + * the subclass of {@code ERXCopyable} to copy the + * relationship's value to + * @param relationship + * the {@link EORelationship} to copy from the {@code source} + * to the {@code destination} + */ + public static void referenceCopyToOneRelationship(T source, T destination, EORelationship relationship) { + String relationshipName = relationship.name(); + ERXEnterpriseObject sourceRelatedEO = (ERXEnterpriseObject) source.valueForKey(relationshipName); + if (sourceRelatedEO != null) { + ERXCopyable.copyLogger.debug("Copying " + sourceRelatedEO.userPresentableDescription() + " object for relationship " + relationshipName); + ERXEnterpriseObject destinationRelatedEO = Utility.referenceCopy(sourceRelatedEO); + destination.addObjectToBothSidesOfRelationshipWithKey(destinationRelatedEO, relationshipName); + } + } + + /** + * This copies to-one related objects from the source + * {@code ERXCopyable} to the destination by reference. Only + * relationships which are class properties are copied. It can be used + * when creating custom implementations of the duplicate() method in + * ERXCopyable. + * + * @param + * the Type of the {@code source} and {@code destination} + * @param source + * the subclass of {@code ERXCopyable} to copy to-many + * {@code relationship}'s values from + * @param destination + * the subclass of {@code ERXCopyable} to copy to-many + * {@code relationship}'s values to + */ + public static void referenceCopyToManyClassRelationships(T source, T destination) { + ERXCopyable.copyLogger.debug("Reference copying all to-many relationships for " + source.userPresentableDescription()); + EOEntity entity = Utility.entity(source); + for (EORelationship relationship : classRelationships(entity)) { + boolean isClassProperty = entity.classProperties().containsObject(relationship); + if (relationship.isToMany() && isClassProperty) { + Utility.referenceCopyToManyRelationship(source, destination, relationship); + } + } + } + + /** + * @param + * the Type of the {@code source} and {@code destination} + * @param source + * the subclass of {@code ERXCopyable} to copy the + * {@code relationship}'s value(s) from + * @param destination + * the subclass of {@code ERXCopyable} to copy the + * {@code relationship}'s value(s) to + * @param relationship + * the {@link EORelationship} to copy values for from the + * {@code source} to the {@code destination} + * @since Feb 10, 2013 + */ + public static void referenceCopyToManyRelationship(T source, T destination, EORelationship relationship) { + String relationshipName = relationship.name(); + @SuppressWarnings("unchecked") + NSArray sourceRelatedEOs = (NSArray) source.valueForKey(relationshipName); + ERXCopyable.copyLogger.debug("Copying " + sourceRelatedEOs.count() + " for relationship " + relationshipName); + for (ERXCopyable sourceRelatedEO : sourceRelatedEOs) { + ERXCopyable.copyLogger.debug("Copying " + sourceRelatedEO.userPresentableDescription() + " for relationship " + relationshipName); + ERXCopyable destinationRelatedEO = Utility.referenceCopy(sourceRelatedEO); + destination.addObjectToBothSidesOfRelationshipWithKey(destinationRelatedEO, relationshipName); + } + } + + /** + * @param + * the Type of the {@code source} and {@code destination} + * @param source + * the subclass of {@code ERXCopyable} to copy attribute + * values from + * @param destination + * the subclass of {@code ERXCopyable} to copy attribute + * values to + * @param relationship + * the {@link EORelationship} to copy from the {@code source} + * to the {@code destination} + */ + public static void shallowCopyRelationship(T source, T destination, EORelationship relationship) { + if (relationship.isToMany()) { + Utility.shallowCopyToManyRelationship(source, destination, relationship); + } + else { + Utility.shallowCopyToOneRelationship(source, destination, relationship); + } + + } + + /** + * Creates a new instance for each of the of the source's related + * objects' Entity and reference copies the attributes and relationships + * to it + * + * @param + * the Type of the {@code source} and {@code destination} + * @param source + * the subclass of {@code ERXCopyable} to copy the + * {@code relationship}'s value(s) from + * @param destination + * the subclass of {@code ERXCopyable} to copy the + * {@code relationship}'s value(s) to + * @param relationship + * the {@link EORelationship} to copy values for from the + * {@code source} to the {@code destination} + * @since Feb 10, 2013 + */ + public static void shallowCopyToManyRelationship(T source, T destination, EORelationship relationship) { + String relationshipName = relationship.name(); + @SuppressWarnings("unchecked") + NSArray sourceRelatedEOs = (NSArray) source.valueForKey(relationshipName); + ERXCopyable.copyLogger.debug("Copying " + sourceRelatedEOs.count() + " for relationship " + relationshipName); + for (ERXCopyable sourceRelatedEO : sourceRelatedEOs) { + ERXCopyable.copyLogger.debug("Copying " + sourceRelatedEO.userPresentableDescription() + " for relationship " + relationshipName); + ERXCopyable destinationRelatedEO = Utility.shallowCopy(sourceRelatedEO); + destination.addObjectToBothSidesOfRelationshipWithKey(destinationRelatedEO, relationshipName); + } + } + + /** + * Creates a new instance of the source's related object's Entity and + * reference copies the attributes and relationships to it + * + * @param + * the Type of the {@code source} and {@code destination} + * @param source + * the subclass of {@code ERXCopyable} to copy the + * relationship's value from + * @param destination + * the subclass of {@code ERXCopyable} to copy the + * relationship's value to + * @param relationship + * the {@link EORelationship} to copy from the {@code source} + * to the {@code destination} + */ + public static void shallowCopyToOneRelationship(T source, T destination, EORelationship relationship) { + String relationshipName = relationship.name(); + ERXCopyable sourceRelatedEO = (ERXCopyable) source.valueForKey(relationshipName); + if (sourceRelatedEO != null) { + ERXCopyable.copyLogger.debug("Copying " + sourceRelatedEO.userPresentableDescription() + " object for relationship " + relationshipName); + ERXCopyable destinationRelatedEO = Utility.shallowCopy(sourceRelatedEO); + destination.addObjectToBothSidesOfRelationshipWithKey(destinationRelatedEO, relationshipName); + } + } + + /** + *

+ * Creates a new instance of the {@code source}'s Entity, then steps + * through all attributes and relationships, copying them as defined in + * each property's UserInfo dictionary in the EOModel. + *

+ * + *

+ * To make use of this method of copying an EO, simply override the + * {@link ERXCopyable#duplicate(NSMutableDictionary) + * duplicate(NSMutableDictionary)} method in your EO with the following: + * + *

+		 * public MyEO duplicate(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> copiedObjects) {
+		 * 	MyEO duplicate = ERXCopyable.Utility.modelCopy(copiedObjects, (MyEO) this);
+		 * 	return duplicate;
+		 * }
+		 * 
+ * + *

+ * + * @param + * the Type of the {@code source} and returned objects + * @param copiedObjects + * the copied objects keyed on the {@link EOGlobalID} of the + * object the copy was made from. + * @param source + * the subclass of {@code ERXCopyable} to copy + * @return a copy of the {@code source} object in the same + * {@link EOEditingContext} + * + * @author David Avendasora + */ + public static T modelCopy(NSMutableDictionary> copiedObjects, T source) { + EOEntity entity = Utility.entity(source); + EOModel model = entity.model(); + NSDictionary entityUserInfo = entity.userInfo(); + String entityName = entity.name(); + String modelName = model.name(); + + if (!entityUserInfo.containsKey(ERXCopyable.ERXCOPYABLE_KEY)) { + String message = "In order to use modelCopy the \"" + ERXCopyable.ERXCOPYABLE_KEY + "\" key must be set in the UserInfo dictionary of the \"" + entityName + "\" Entity in the " + modelName + " EOModel."; + throw new IllegalStateException(message); + } + ERXCopyable.copyLogger.debug("Making copy of " + source + " based on UserInfo settings in the " + modelName + " EOModel"); + T copy = Utility.newInstance(source); + + // Register this object right away to handle circular relationships + copiedObjects.setObjectForKey(copy, Utility.globalIDForObject(source)); + + for (EOProperty property : entity.classProperties()) { + if (property instanceof EOAttribute) { + EOAttribute attribute = (EOAttribute) property; + if (exposedPKAndFKAttributes(source).containsObject(attribute)) { + copy.takeStoredValueForKey(null, attribute.name()); + } + else { + Utility.modelCopyAttribute(source, copy, attribute); + } + } + else { + EORelationship relationship = (EORelationship) property; + Utility.modelCopyRelationship(copiedObjects, source, copy, relationship); + } + } + return copy; + } + + /** + * Reads the values set in the EOModel's attribute and relationship + * UserInfo dictionaries and then uses them to control how the + * {@code source} object is copied. + * + * @param + * the Type of the {@code source} and {@code destination} + * @param copiedObjects + * the copied objects keyed on the {@link EOGlobalID} of the + * object the copy was made from. + * @param source + * the subclass of {@code ERXCopyable} to copy all (to-one + * and to-many) class relationships values from + * @param destination + * the subclass of {@code ERXCopyable} to copy all (to-one + * and to-many) class relationships values to + */ + public static void modelCopyClassRelationships(NSMutableDictionary> copiedObjects, T source, T destination) { + ERXCopyable.copyLogger.debug("Model-copying class relationships for " + source.userPresentableDescription()); + for (EORelationship relationship : Utility.classRelationships(Utility.entity(source))) { + Utility.modelCopyRelationship(copiedObjects, source, destination, relationship); + } + } + + /** + * Reads the values set in the EOModel's attribute and relationship + * UserInfo dictionaries and then uses them to control how the + * {@code source} object is copied. + * + * @param + * the Type of the {@code source} and {@code destination} + * objects + * @param copiedObjects + * the copied objects keyed on the {@link EOGlobalID} of the + * object the copy was made from. the Type of the + * {@code source} and {@code destination} + * @param source + * the subclass of {@code ERXCopyable} to copy the + * {@code relationship}'s value(s) from + * @param destination + * the subclass of {@code ERXCopyable} to copy the + * {@code relationship}'s value(s) to + * @param relationship + * the {@link EORelationship} to copy values for from the + * {@code source} to the {@code destination} + */ + public static void modelCopyRelationship(NSMutableDictionary> copiedObjects, T source, T destination, EORelationship relationship) { + String relationshipName = relationship.name(); + CopyType copyType = Utility.copyType(relationship); + ERXCopyable.copyLogger.debug("CopyType \"" + copyType.type() + "\" specified for " + relationshipName); + switch (copyType) { + case REFERENCE: + Utility.referenceCopyRelationship(source, destination, relationship); + break; + case SHALLOW: + Utility.shallowCopyRelationship(source, destination, relationship); + break; + case DEEP: + Utility.deepCopyRelationship(copiedObjects, source, destination, relationship); + break; + case NULLIFY: + destination.takeStoredValueForKey(null, relationshipName); + break; + default: + handleMissingOrInvalidCopyType(relationship, copyType); + } + } + + /** + * @param + * the Type of the {@code source} and {@code destination} + * objects + * @param source + * the subclass of {@code ERXCopyable} to copy the class + * attribute values from + * @param destination + * the subclass of {@code ERXCopyable} to copy the class + * attribute values to + */ + public static void modelCopyClassAttributes(T source, T destination) { + ERXCopyable.copyLogger.debug("Model-copying class attributes for " + source.userPresentableDescription()); + NSArray attributesToCopy = Utility.classAttributes(Utility.entity(source)); + for (EOAttribute attribute : attributesToCopy) { + modelCopyAttribute(source, destination, attribute); + } + } + + /** + * @param + * the Type of the {@code source} and {@code destination} + * objects + * @param source + * the subclass of {@code ERXCopyable} to copy the + * {@code attribute}'s value from + * @param destination + * the subclass of {@code ERXCopyable} to copy the + * {@code attribute}'s value to + * @param attribute + * the {@link EOAttribute} that should have its value copied + * from the {@code source} to the {@code destination} + */ + public static void modelCopyAttribute(T source, T destination, EOAttribute attribute) { + String attributeName = attribute.name(); + CopyType copyType = Utility.copyType(attribute); + switch (copyType) { + case REFERENCE: + Utility.copyAttribute(source, destination, attribute); + break; + case CURRENT_TIMESTAMP: + destination.takeStoredValueForKey(new NSTimestamp(), attributeName); + break; + case NULLIFY: + destination.takeStoredValueForKey(null, attributeName); + break; + case DEEP: + handleMissingOrInvalidCopyType(attribute, copyType); + break; + default: + handleMissingOrInvalidCopyType(attribute, copyType); + } + } + + /** + * @param property + * the attribute or relationship being copied + * @return the {@link CopyType} value specified for the + * {@code ERXCopyable.CopyType} key in the {@code property}'s + * UserInfo dictionary in the EOModel + */ + public static CopyType copyType(EOProperty property) { + CopyType copyType; + if (property instanceof EOAttribute) { + EOAttribute attribute = (EOAttribute) property; + @SuppressWarnings("unchecked") + NSDictionary userInfo = attribute.userInfo(); + copyType = Utility.copyType(attribute, userInfo); + } + else { + EORelationship relationship = (EORelationship) property; + NSDictionary userInfo = relationship.userInfo(); + copyType = Utility.copyType(relationship, userInfo); + } + return copyType; + } + + /** + * Abstracted out of {@link #copyType(EOProperty)} to handle exceptions + * if the userInfo dictionary is null, there is no + * {@code ERXCopyable.CopyType} key, or the value for the key is null or + * invalid. + * + * @param property + * the attribute or relationship being copied + * @param userInfo + * the {@code property}'s UserInfo dictionary that contains a + * {@code ERXCopyable.CopyType} key + * @return the {@link CopyType} value specified for the + * {@code ERXCopyable.CopyType} key in {@code userInfo} + * dictionary for {@code property}'s UserInfo dictionary in the + * EOModel + */ + public static CopyType copyType(EOProperty property, NSDictionary userInfo) { + if (userInfo == null) { + Utility.handleMissingOrInvalidCopyType(property, null); + } + String userInfoKey = ERXCopyable.COPY_TYPE_KEY; + @SuppressWarnings("null") + String copyTypeString = (String) userInfo.objectForKey(userInfoKey); + CopyType copyType = (CopyType.get(copyTypeString)); + if (copyType == null) { + Utility.handleMissingOrInvalidCopyType(property, copyType); + } + return copyType; + } + + public static void handleMissingERXCopyableKey(Class invalidClass) { + String exceptionMessage = "To use ERXCopyable with " + invalidClass.getSimpleName() + " it must implement " + ERXCopyable.class.getSimpleName() + "."; + throw new IllegalStateException(exceptionMessage); + } + + /** + * Creates a meaningful error message to be displayed when + * {@code modelCopy} was specified for the {@code ERXCopyable} key in an + * Entity's UserInfo dictionary, but not all attributes and + * relationships have a valid {@code ERXCopyable.CopyType} specified. + * + * @param property + * the attribute or relationship being copied + * @param copyType + * the invalid {@link CopyType} specified in the + * {@code property}'s UserInfo dictionary in the EOModel + */ + public static void handleMissingOrInvalidCopyType(EOProperty property, CopyType copyType) { + String propertyType = Utility.propertyType(property); + @SuppressWarnings("unchecked") + NSArray copyTypes = (NSArray) Utility.copyTypes(property).valueForKey("type"); + String validCopyTypes = copyTypes.componentsJoinedByString(", "); + String propertyName = property.name(); + EOEntity entity = property.entity(); + String entityName = entity.name(); + EOModel model = entity.model(); + String modelName = model.name(); + String exceptionMessage = "To use ERXCopyable's modelCopy methods the \"" + ERXCopyable.COPY_TYPE_KEY + "\" key must be set in the UserInfo dictionary of the \"" + entityName + "." + propertyName + "\" " + propertyType + " in " + modelName + " AND it must be set to one of these values: {" + validCopyTypes + "}. " + copyType + " is not a valid value."; + throw new IllegalStateException(exceptionMessage); + } + + /** + * @param property + * the attribute or relationship being copied + * @return "attribute" if the passed in property is an instance of + * {@link EOAttribute}, "relationship" if it is an instance of + * {@link EORelationship} + */ + public static String propertyType(EOProperty property) { + String propertyType; + if (property instanceof EOAttribute) { + propertyType = "attribute"; + } + else { + propertyType = "relationship"; + } + return propertyType; + } + + /** + * @param property + * the attribute or relationship being copied + * @return an array of the valid {@link CopyType}s for the passed-in + * {@link EOProperty} + */ + public static NSArray copyTypes(EOProperty property) { + NSArray validCopyTypes; + if (property instanceof EOAttribute) { + validCopyTypes = new NSArray(CopyType.NULLIFY, CopyType.CURRENT_TIMESTAMP, CopyType.REFERENCE); + } + else { + validCopyTypes = new NSArray(CopyType.NULLIFY, CopyType.REFERENCE, CopyType.SHALLOW, CopyType.DEEP); + } + return validCopyTypes; + } + + /** + * @param + * the Type of the {@code source} object + * @param source + * the subclass of {@code ERXCopyable} that is being copied + * @return an array of {@link EOAttribute}s that are the Primary- and + * Foreign-Key attributes for the {@code source} subclass of + * {@link ERXCopyable} + */ + public static NSArray primaryAndForeignKeyAttributes(T source) { + EOEntity entity = Utility.entity(source); + NSArray primaryKeyAttributes = entity.primaryKeyAttributes(); + NSMutableSet keyAttributes = new NSMutableSet(primaryKeyAttributes); + NSArray classRelationships = Utility.classRelationships(entity); + for (EORelationship relationship : classRelationships) { + NSArray foreignKeyAttributes = relationship.sourceAttributes(); + keyAttributes.addObjectsFromArray(foreignKeyAttributes); + } + NSArray primaryAndForeignKeyAttributes = keyAttributes.allObjects(); + return primaryAndForeignKeyAttributes; + } + } + + public static Logger copyLogger = LoggerFactory.getLogger(ERXCopyable.class); + + /** + * "{@link ERXCopyable}" which is the exact String that must be used as the + * key in the Entity's EOModel UserInfo dictionary to designate it as + * implementing {@link ERXCopyable} interface. + */ + public static final String ERXCOPYABLE_KEY = ERXCopyable.class.getSimpleName(); + + /** + * "{@code ERXCopyable.CopyType}" which is the exact String that must be + * used as the key in an Attribute's or Relationship's EOModel UserInfo + * dictionary to specify the way the property should be copied by + * {@link ERXCopyable}. + */ + public static final String COPY_TYPE_KEY = ERXCOPYABLE_KEY + "." + CopyType.class.getSimpleName(); + + /** + * Convenience cover method for {@link #copy(NSMutableDictionary)} that + * creates the dictionary for you. You can use this to start the copying of + * a graph if you have no need to reference the dictionary. + * + * @return a copy of this object + */ + public T copy(); + + /** + * Returns a copy of this object, copying related objects as well. The + * actual copy mechanism (by reference, deep, or custom) for each object is + * up to the object being copied. If a copy already exists in + * {@code copiedObjects}, then that copy is returned instead of making a new + * copy. This allows complex graphs of objects, including those with cycles, + * to be copied without producing duplicate objects. The graph of copied + * objects will be the same regardless of where copy is started with two + * exceptions: if it is started on a reference copied object or if a + * reference copied object is the only path between two disconnected parts + * of the graph. In these cases the reference copied object prevents the + * copy from following the graph further. + * + * @param copiedObjects + * the copied objects keyed on the {@link EOGlobalID} of the + * object the copy was made from. + * @return a copy of this object + */ + public T copy(NSMutableDictionary> copiedObjects); + + /** + * Returns a copy of this object. Each {@code ERXCopyable} should implement + * this to produce the actual copy by an appropriate mechanism (reference, + * shallow, deep, or custom). + * + * @param copiedObjects + * the copied objects keyed on the {@link EOGlobalID} of the + * object the copy was made from. + * @return a copy of this object + */ + public T duplicate(NSMutableDictionary> copiedObjects); +}