package er.extensions.eof; import java.util.UUID; 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. * <p> * There are several ways to implement this interface: * <ol> * <li>Manually implement the interface in each {@code Entity.java} class that * you want to be able to copy<br>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: * * <pre><code> * @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; * } * </code></pre> * * </li> * <li>Manually implement the interface in a {@link ERXGenericRecord} subclass * <br>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: * * <pre><code> * @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; * } * </code></pre> * * </li> * <li>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.<br>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: * <ol> * <li>Create or modify your own EOGenerator template with the following * additions: * <ul> * <li>In the imports section, add the following: * * <pre> * #if(${entity.userInfo.ERXCopyable}) * import er.extensions.eof.ERXCopyable; * #end * </pre> * * </li> * <li>At the very end of the class declaration line (just prior to the opening * '{') insert the following: * * <pre> * #if(${entity.userInfo.ERXCopyable}) implements ERXCopyable<${entity.classNameWithOptionalPackage}>#end * </pre> * * </li> * <li>Somewhere in the body of the class (immediately before the closing '}' at * the end of the template is easiest) insert the following: * * <pre> * #if(${entity.userInfo.ERXCopyable} == 'Model' || ${entity.userInfo.ERXCopyable} == 'Default') * * * public ${entity.classNameWithOptionalPackage} copy() { * $entity.classNameWithOptionalPackage copy = copy(new NSMutableDictionary<EOGlobalID, ERXCopyable<?>>()); * return copy; * } * * public ${entity.classNameWithOptionalPackage} copy(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> copiedObjects) { * ${entity.classNameWithOptionalPackage} copy = duplicate(copiedObjects); * return copy; * } * * public ${entity.classNameWithOptionalPackage} duplicate(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> copiedObjects) { * #if(${entity.userInfo.ERXCopyable} == 'Model') * ${entity.classNameWithOptionalPackage} duplicate = ERXCopyable.Utility.modelCopy(copiedObjects, (${entity.classNameWithOptionalPackage}) this); * #elseif(${entity.userInfo.ERXCopyable} == 'Default') * ${entity.classNameWithOptionalPackage} duplicate = ERXCopyable.Utility.deepCopy(copiedObjects, (${entity.classNameWithOptionalPackage}) this); * #end * return duplicate; * } * #end * </pre> * * </li> * </ul> * </li> * </ol> * </ol> * * @param <T> * the specific subclass of {@code ERXCopyable} that is being copied * * @author Chuck Hill * @author Sacha Mallais * @author David Avendasora */ public interface ERXCopyable<T extends ERXCopyable<T>> extends ERXEnterpriseObject { /** * <p> * {@link Enum} that specifies the valid ways in which an {@link EOModel}'s * {@link EOProperty}s can be copied. * </p> * <p> * 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. * </p> * * @author David Avendasora */ public enum CopyType { /** * Stored as " {@code ERXCopyable.CopyType = Skip;}" in the * property's UserInfo dictionary. Skips the value. * <em>For attributes only.</em> */ SKIP("Skip", new NSArray<Class<? extends EOProperty>>(EOAttribute.class, EORelationship.class)), /** * Stored as " {@code ERXCopyable.CopyType = Nullify;}" in the * property's UserInfo dictionary. Sets the {@code copy}'s value to * <code>null</code>. This setting <b>does not</b> copy the * {@code original}'s value. <em>For attributes and relationships.</em> */ NULLIFY("Nullify", new NSArray<Class<? extends EOProperty>>(EOAttribute.class, EORelationship.class)), /** * 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 <b>same</b> destination * object as the {@code original}'s relationship does. <b>WARNING:</b> * if you use this on a non-flattening, to-many relationship, the * destination objects will be <b>moved</b> from the {@code original} to * the {@code copy}. <em>For attributes and relationships.</em> */ REFERENCE("Reference", new NSArray<Class<? extends EOProperty>>(EOAttribute.class, EORelationship.class)), /** * 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. * <em>For relationships only.</em> */ SHALLOW("Shallow", new NSArray<Class<? extends EOProperty>>(EORelationship.class)), /** * 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. * <em>For relationships only.</em> */ DEEP("Deep", new NSArray<Class<? extends EOProperty>>(EORelationship.class)), /** * Stored as " {@code ERXCopyable.CopyType = CurrentTimestamp;}" in the * {@link EOAttribute}'s UserInfo dictionary. This setting <b>does * not</b> copy the {@code original}'s value. It sets the {@code copy}'s * value to the <em>current</em> date and time using * {@code new NSTimestamp()} <em>For attributes only.</em> */ CURRENT_TIMESTAMP("CurrentTimestamp", new NSArray<Class<? extends EOProperty>>(EOAttribute.class)), /** * Stored as " {@code ERXCopyable.CopyType = UUID;}" in the * {@link EOAttribute}'s UserInfo dictionary. This setting <b>does * not</b> copy the {@code original}'s value. It sets the {@code copy}'s * value to a newly generated {@link java.util.UUID} using * {@link java.util.UUID#randomUUID()} <em>For attributes only.</em> */ UUID("UUID", new NSArray<Class<? extends EOProperty>>(EOAttribute.class)); private final String _type; private final NSArray<Class<? extends EOProperty>> _validPropertyClasses; CopyType(String type, NSArray<Class<? extends EOProperty>> propertyTypes) { _type = type; _validPropertyClasses = propertyTypes; } public String type() { return _type; } /** * @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<CopyType> copyTypesFor(EOProperty property) { NSMutableArray<CopyType> validCopyTypes = new NSMutableArray<>(); for (CopyType copyType : values()) { if (copyType.validPropertyClasses().contains(property.getClass())) { validCopyTypes.add(copyType); } } return validCopyTypes.immutableClone(); } /** * @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()) || typeAsString.equalsIgnoreCase(ct.name())) { copyType = ct; break; } } } return copyType; } public NSArray<Class<? extends EOProperty>> validPropertyClasses() { return _validPropertyClasses; } } /** * This class provides a default implementation of ERXCopyable that handles * the most common situations encountered copying {@link EOEnterpriseObject} * s. * <h3>Notes</h3> * <ul> * <li>Debugging information can be turned on with the DEBUG level of the * log4j logger * * <pre> * er.extensions.eof.ERXCopyable}. * </pre> * * </li> * <li>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: * * <pre><code> * EOGlobalID globalID = editingContext().globalIDForObject(this); * copiedObjects.setObjectForKey(copy, globalID); * </code></pre> * * </li> * </ul> */ 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 <code>copiedObjects</code> 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 <T> * 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 */ public static <T extends ERXCopyable<T>> T copy(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> copiedObjects, T source) { EOGlobalID globalID = source.editingContext().globalIDForObject(source); ERXCopyable.copyLogger.debug("Copying object " + source.userPresentableDescription()); @SuppressWarnings("unchecked") T copy = (T) copiedObjects.objectForKey(globalID); if (copy == null) { ERXCopyable.copyLogger.debug("Creating duplicate."); copy = 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 <T> * 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 extends ERXCopyable<T>> T duplicate(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> 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 * <ul> * <li>{@link Utility#modelCopy(NSMutableDictionary, ERXCopyable)}</li> * <li>{@link Utility#referenceCopy(ERXEnterpriseObject)}</li> * <li>{@link Utility#shallowCopy(ERXCopyable)}</li> * <li>{@link Utility#deepCopy(NSMutableDictionary, ERXCopyable)}</li> * </ul> * 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:<br> * <ol> * <li>An attribute or relationship must not be copied (e.g. order numbers). * </li> * <li>An attribute or relationship needs special handling (e.g. * dateModified should reflect when the copy was made, not when the original * object was created).</li> * <li>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).</li> * <li>The relationships must be copied in a certain order (e.g. due to side * effects in the methods setting the relationships).</li> * </ol> * 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. * * <p> * Debugging information can be turned on with the DEBUG level of the log4j * logger <code>er.extensions.eof.ERXCopyable</code>. * </p> */ public static class Utility { protected static volatile NSMutableDictionary<String, NSArray<EOAttribute>> _exposedPKAndFKAttributeDictionary = null; protected static volatile NSMutableDictionary<String, NSArray<EOAttribute>> _classAttributesDictionary = null; protected static volatile NSMutableDictionary<String, NSArray<EORelationship>> _classRelationshipsDictionary = null; /** * Returns the entity for the current object. Defers to * {@link ERXEOAccessUtilities#entityNamed(EOEditingContext, String) * ERXEOAccessUtilities.entityNamed()} for the actual work. * * @param <T> * the Type of the {@code enterpriseObject} * @param enterpriseObject * * @return {@link EOEntity} of the {@code enterpriseObject} */ public static <T extends ERXEnterpriseObject> 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. * <ol> * <li>All objects are disconnected from the relationship.</li> * <li>If a disconnected object has a temporary EOGlobalID it is * deleted.</li> * </ol> * * @param <T> * 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 <T extends ERXEnterpriseObject> 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<ERXEnterpriseObject> relatedObjects = (NSArray<ERXEnterpriseObject>) destination.valueForKey(relationshipName); if (relatedObjects.count() > 0) { entity.relationshipNamed(relationshipName); ERXCopyable.copyLogger.debug("Removing objects in to-many relationship " + relationshipName); for (ERXEnterpriseObject relatedObject : relatedObjects) { destination.removeObjectFromBothSidesOfRelationshipWithKey(relatedObject, relationshipName); if (relatedObject.isNewObject()) { editingContext.deleteObject(relatedObject); } } } } // To-one relationships for (String relationshipName : destination.toOneRelationshipKeys()) { ERXEnterpriseObject relatedObject = (ERXEnterpriseObject) 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 <em>are not</em> copied. Such * attributes are set to null. See * {@link #exposedPKandFKAttributeNames(ERXCopyable)} for details on how * this is determined. It can be used when creating custom * implementations of the * {@link ERXCopyable#duplicate(NSMutableDictionary)} method. * * @param <T> * 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 <T extends ERXEnterpriseObject> void copyClassAttributes(T source, T destination) { EOEntity entity = Utility.entity(source); ERXCopyable.copyLogger.debug("Copying all attributes for " + source.userPresentableDescription()); NSArray<EOAttribute> attributes = Utility.classAttributes(entity); for (EOAttribute attribute : attributes) { Utility.copyAttribute(source, destination, attribute); } } /** * @param <T> * 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 <T extends ERXEnterpriseObject> void copyAttribute(T source, T destination, EOAttribute attribute) { String attributeName = attribute.name(); Object sourceValue = source.storedValueForKey(attributeName); NSArray<EOAttribute> 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 <T> * 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 extends ERXCopyable<T>> T deepCopy(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> 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 <T> * 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 <T extends ERXCopyable<T>> void deepCopyClassRelationships(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> copiedObjects, T source, T destination) { ERXCopyable.copyLogger.debug("Deep copying relationships for " + source.userPresentableDescription()); EOEntity entity = Utility.entity(source); NSArray<EORelationship> 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<EORelationship> classRelationships(EOEntity entity) { String entityName = entity.name(); if (Utility._classRelationshipsDictionary == null) { Utility._classRelationshipsDictionary = new NSMutableDictionary<String, NSArray<EORelationship>>(); } NSArray<EORelationship> classRelationships = Utility._classRelationshipsDictionary.objectForKey(entityName); if (classRelationships == null) { NSArray<EORelationship> allRelationships = entity.relationships(); NSMutableArray<EORelationship> 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 <T> * 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 <T extends ERXCopyable<T>> void deepCopyRelationship(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> copiedObjects, T source, T destination, EORelationship relationship) { if (relationship.isToMany()) { Utility.deepCopyToManyRelationship(copiedObjects, source, destination, relationship); } else { Utility.deepCopyToOneRelationship(copiedObjects, source, destination, relationship); } } /** * @param <T> * 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 <T extends ERXCopyable<T>> void deepCopyToOneRelationship(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> 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 <T> * 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 <T extends ERXCopyable<T>> void deepCopyToManyRelationship(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> 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<ERXCopyable> originals = ((NSArray<ERXCopyable>) source.valueForKey(relationshipName)).immutableClone(); @SuppressWarnings("unchecked") NSArray<ERXCopyable> destinationInitialRelatedObjects = ((NSArray<ERXCopyable>) destination.valueForKey(relationshipName)).immutableClone(); ERXCopyable.copyLogger.debug("Copying " + originals.count() + " object(s) for relationship " + relationshipName); for (ERXCopyable original : originals) { try { ERXCopyable copy = 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); } } } catch (ClassCastException e) { String message = original.entityName() + " does not impliment " + ERXCopyable.class.getCanonicalName() + ". If you are using the Standard mode, you must manually add the implements clause to the class delcaration in " + original.getClass().getSimpleName() + ".java. If you are using the \"Model\" mode you must have an 'ERXCopyable = Model' entry in " + original.entityName() + "'s UserInfo dictionary in the EOModel. (Originally thrown exception message: " + e.getMessage() + ")"; throw new RuntimeException(message, e); } } } /** * 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 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<String> exposedPKandFKAttributeNames(ERXEnterpriseObject source) { @SuppressWarnings("unchecked") NSArray<String> attributeNames = (NSArray<String>) 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 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<EOAttribute> exposedPKAndFKAttributes(ERXEnterpriseObject source) { EOEntity entity = Utility.entity(source); String entityName = entity.name(); if (Utility._exposedPKAndFKAttributeDictionary == null) { Utility._exposedPKAndFKAttributeDictionary = new NSMutableDictionary<String, NSArray<EOAttribute>>(); } NSArray<EOAttribute> 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<EOAttribute> classAttributes = classAttributes(entity); NSArray<EOAttribute> 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<EOAttribute> classAttributes(EOEntity entity) { String entityName = entity.name(); if (Utility._classAttributesDictionary == null) { Utility._classAttributesDictionary = new NSMutableDictionary<String, NSArray<EOAttribute>>(); } NSArray<EOAttribute> classAttributes = Utility._classAttributesDictionary.objectForKey(entityName); if (classAttributes == null) { NSArray<EOAttribute> allAttributes = entity.attributes(); NSMutableArray<EOAttribute> 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(ERXEnterpriseObject enterpriseObject) { EOGlobalID globalID = enterpriseObject.editingContext().globalIDForObject(enterpriseObject); return globalID; } /** * This creates and returns a new, <em>completely empty</em> instance of * the same Entity as source. * <p> * The reason that copying should use this method is because 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 awakeFromInsertion(). * Preset/default attributes will 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. * </p> * * <p> * This method is for use in custom implementations of * {@link ERXCopyable#duplicate(NSMutableDictionary)}. * </p> * * @param <T> * 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 extends ERXEnterpriseObject> T newInstance(T source) { 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 simply returns the * receiver. This method of copying is suitable for lookup list / * enumeration items and other objects which should never be duplicated. * * @param <T> * the Type of the {@code source} * @param source * the subclass of {@code ERXCopyable} to copy * @return a copy of this object */ public static <T extends ERXEnterpriseObject> 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 <T> * the Type of the {@code source} * @param source * the subclass of {@code ERXCopyable} to copy * @return a copy of this object */ public static <T extends ERXEnterpriseObject> 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 all objects for all class-property relationships from the * {@code source} onto the {@code destination}. It can be used when * creating custom implementations of * {@link ERXCopyable#duplicate(NSMutableDictionary)}. * * @param <T> * 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 <T extends ERXEnterpriseObject> 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 <T> * 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 <T extends ERXEnterpriseObject> 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 <T> * 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 <T extends ERXEnterpriseObject> 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 <T> * the Type of the {@code source} and {@code destination} * @param <E> * @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 <T extends ERXEnterpriseObject, E extends ERXEnterpriseObject> void referenceCopyToOneRelationship(T source, T destination, EORelationship relationship) { String relationshipName = relationship.name(); @SuppressWarnings("unchecked") E sourceRelatedEO = (E) source.valueForKey(relationshipName); if (sourceRelatedEO != null) { ERXCopyable.copyLogger.debug("Copying " + sourceRelatedEO.userPresentableDescription() + " object for relationship " + relationshipName); E 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 <T> * 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 <T extends ERXEnterpriseObject> 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 <T> * the Type of the {@code source} and {@code destination} * @param <E> * @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 <T extends ERXEnterpriseObject, E extends ERXEnterpriseObject> void referenceCopyToManyRelationship(T source, T destination, EORelationship relationship) { String relationshipName = relationship.name(); @SuppressWarnings("unchecked") NSArray<E> sourceRelatedEOs = (NSArray<E>) source.valueForKey(relationshipName); ERXCopyable.copyLogger.debug("Copying " + sourceRelatedEOs.count() + " for relationship " + relationshipName); for (E sourceRelatedEO : sourceRelatedEOs) { ERXCopyable.copyLogger.debug("Copying " + sourceRelatedEO.userPresentableDescription() + " for relationship " + relationshipName); E destinationRelatedEO = Utility.referenceCopy(sourceRelatedEO); destination.addObjectToBothSidesOfRelationshipWithKey(destinationRelatedEO, relationshipName); } } /** * @param <T> * 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 <T extends ERXEnterpriseObject> 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 <T> * 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 <T extends ERXEnterpriseObject> void shallowCopyToManyRelationship(T source, T destination, EORelationship relationship) { String relationshipName = relationship.name(); @SuppressWarnings("unchecked") NSArray<ERXEnterpriseObject> sourceRelatedEOs = (NSArray<ERXEnterpriseObject>) source.valueForKey(relationshipName); ERXCopyable.copyLogger.debug("Copying " + sourceRelatedEOs.count() + " for relationship " + relationshipName); for (ERXEnterpriseObject sourceRelatedEO : sourceRelatedEOs) { ERXCopyable.copyLogger.debug("Copying " + sourceRelatedEO.userPresentableDescription() + " for relationship " + relationshipName); ERXEnterpriseObject 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 <T> * 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 <T extends ERXEnterpriseObject> 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. * <p> * 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: * * <pre><code> * public MyEO duplicate(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> copiedObjects) { * MyEO duplicate = ERXCopyable.Utility.modelCopy(copiedObjects, (MyEO) this); * return duplicate; * } * </code></pre> * * @param <T> * 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 extends ERXCopyable<T>> T modelCopy(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> copiedObjects, T source) { EOEntity entity = Utility.entity(source); EOModel model = entity.model(); NSDictionary<String, Object> 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 <T> * 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 <T extends ERXCopyable<T>> void modelCopyClassRelationships(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> 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 <T> * 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 <T extends ERXCopyable<T>> void modelCopyRelationship(NSMutableDictionary<EOGlobalID, ERXCopyable<?>> 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 <T> * 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 <T extends ERXCopyable<T>> void modelCopyClassAttributes(T source, T destination) { ERXCopyable.copyLogger.debug("Model-copying class attributes for " + source.userPresentableDescription()); NSArray<EOAttribute> attributesToCopy = Utility.classAttributes(Utility.entity(source)); for (EOAttribute attribute : attributesToCopy) { modelCopyAttribute(source, destination, attribute); } } /** * @param <T> * 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 <T extends ERXCopyable<T>> 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 UUID: destination.takeStoredValueForKey(UUID.randomUUID(), attributeName); break; case NULLIFY: destination.takeStoredValueForKey(null, attributeName); break; case SKIP: // nothig to do 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<String, Object> userInfo = attribute.userInfo(); copyType = Utility.copyType(attribute, userInfo); } else { EORelationship relationship = (EORelationship) property; NSDictionary<String, Object> 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<String, Object> 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<String> copyTypes = (NSArray<String>) CopyType.copyTypesFor(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 = "ERXCopyable's modelCopy requires the \"" + ERXCopyable.COPY_TYPE_KEY + "\" key must be set in the UserInfo dictionary of \"" + 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 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<EOAttribute> primaryAndForeignKeyAttributes(ERXEnterpriseObject source) { EOEntity entity = Utility.entity(source); NSArray<EOAttribute> primaryKeyAttributes = entity.primaryKeyAttributes(); NSMutableSet<EOAttribute> keyAttributes = new NSMutableSet<>(primaryKeyAttributes); NSArray<EORelationship> classRelationships = Utility.classRelationships(entity); for (EORelationship relationship : classRelationships) { NSArray<EOAttribute> foreignKeyAttributes = relationship.sourceAttributes(); keyAttributes.addObjectsFromArray(foreignKeyAttributes); } NSArray<EOAttribute> 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<EOGlobalID, ERXCopyable<?>> 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<EOGlobalID, ERXCopyable<?>> copiedObjects); }