/* * JBoss, Home of Professional Open Source. * * See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing. * * See the AUTHORS.txt file distributed with this work for a full listing of individual contributors. */ package org.teiid.designer.core; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.edit.command.RemoveCommand; import org.eclipse.emf.edit.command.SetCommand; import org.eclipse.emf.edit.domain.EditingDomain; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.designer.core.search.ModelWorkspaceSearch; import org.teiid.designer.core.search.runtime.ReferencesRecord; import org.teiid.designer.core.util.ModelVisitor; /** * This class finds all references to the deleted object and it's contents, and removes or unsets those references. * * @see org.teiid.designer.core.FindRelatedObjectsToDeleted * @see org.teiid.designer.core.ModelEditor#delete(EObject) * * @since 8.0 */ public class ClearReferencesUponDelete implements ModelVisitor { private final EditingDomain editingDomain; private final List additionalCommands; private final Collection allDeletedObjects; private final HashMap featureValueRemoves; // key = referencing EObject, value = Map (key = feature, value = list of values) private final ModelWorkspaceSearch workspaceSearch; /** * Cache of features by owner that have unset commands already created. Keyed by EObject. Value is a list of features. */ private Map objectFeatureUnsetMap = new HashMap(); /** * Construct an instance of FindRelatedObjectsToDeleted. */ public ClearReferencesUponDelete( final Collection allDeletedObjects, final EditingDomain editingDomain ) { this(allDeletedObjects, editingDomain, new ModelWorkspaceSearch()); } /** * Construct an instance of FindRelatedObjectsToDeleted. */ public ClearReferencesUponDelete( final Collection allDeletedObjects, final EditingDomain editingDomain, final ModelWorkspaceSearch workspaceSearch ) { CoreArgCheck.isNotNull(editingDomain); CoreArgCheck.isNotNull(allDeletedObjects); CoreArgCheck.isNotNull(workspaceSearch); this.additionalCommands = new LinkedList(); this.editingDomain = editingDomain; this.allDeletedObjects = allDeletedObjects; this.featureValueRemoves = new HashMap(); this.workspaceSearch = workspaceSearch; } /** * Determines if a remove command is needed for the specified referencing object (owner) by checking to see if the specified * value for the specified feature has already been processed. * * @param theReferencingObject the object whose feature is referenceing the specified value * @param theFeature the feature * @param theValue the value being deleted * @return <code>true</code>if a remove command should be created; <code>false</code> otherwise. * @since 4.2 */ private boolean isRemoveCommandNeeded( EObject theReferencingObject, EStructuralFeature theFeature, Object theValue ) { boolean result = false; Map featureMap = (Map)this.featureValueRemoves.get(theReferencingObject); List values = null; if (featureMap == null) { // first time this referencing object has had a feature value removed featureMap = new HashMap(); this.featureValueRemoves.put(theReferencingObject, featureMap); } if (featureMap.containsKey(theFeature)) { // previous remove processed for this feature values = (List)featureMap.get(theFeature); } else { // first time this feature has had a value removed values = new ArrayList(1); featureMap.put(theFeature, values); } if (!values.contains(theValue)) { // feature value has not been processed values.add(theValue); result = true; } return result; } /** * Removes the deleted object from a list of referenced objects if the specified feature has multiple values. If the feature * is single valued the feature is unset. * * @param theReferencingObject the referencing object that references the object being deleted * @param theFeatureReference the referencing feature * @param theDeletedObject the object being removed * @since 4.2 */ private void removeReference( Object theReferencingObject, EReference theFeatureReference, EObject theDeletedObject ) { if ((theReferencingObject instanceof EObject) && !this.allDeletedObjects.contains(theReferencingObject)) { // remove the reference to the object being deleted or unset if not multivalued if (theFeatureReference.isMany() && !theFeatureReference.isVolatile()) { remove((EObject)theReferencingObject, theFeatureReference, theDeletedObject); } else { unset((EObject)theReferencingObject, theFeatureReference); } } } /** * Visit a deleted object and remove/unset references from non-deleted objects to it. * * @param object one of the objects being deleted * @see org.teiid.designer.core.util.ModelVisitor#visit(org.eclipse.emf.ecore.EObject) */ @Override public boolean visit( final EObject object ) { // Find all references ... final EClass eclass = object.eClass(); final Collection allRefs = eclass.getEAllReferences(); for (final Iterator iter = allRefs.iterator(); iter.hasNext();) { final EReference reference = (EReference)iter.next(); final EReference opposite = reference.getEOpposite(); // Process only non-containment references ... // remove reference to deleted object from opposite if (!reference.isContainment() && opposite != null && !opposite.isContainment()) { if (reference.isMany()) { Collection values = (Collection)object.eGet(reference); for (final Iterator valueIter = values.iterator(); valueIter.hasNext();) { removeReference(valueIter.next(), opposite, object); } } else if (opposite.isChangeable()) { removeReference(object.eGet(reference), opposite, object); } } } // clean up unidirectional references ModelEditorImpl modelEditor = (ModelEditorImpl)ModelerCore.getModelEditor(); // get objectID and find uni-directional references for that ID String objID = modelEditor.getSearchIndexObjectID(object); if (objID != null) { // unidirectional references for each object to clean up Collection refRecords = workspaceSearch.getUniDirectionalReferencesTo(objID); if (refRecords != null && !refRecords.isEmpty()) { for (final Iterator refIter = refRecords.iterator(); refIter.hasNext();) { ReferencesRecord refRecord = (ReferencesRecord)refIter.next(); String refUUID = refRecord.getUUID(); EObject refrencingObj = modelEditor.findObject(refUUID); if (refrencingObj != null) { cleanupUniDirectionalReferences(refrencingObj, object); } } } } return true; } /** * @see org.teiid.designer.core.util.ModelVisitor#visit(org.eclipse.emf.ecore.resource.Resource) * @since 4.2 */ @Override public boolean visit( Resource resource ) { return resource != null; } /* * Clean up uni-directional references from the referencing object to the referenced object. */ private void cleanupUniDirectionalReferences( final EObject referencingObj, final EObject referencedObj ) { // if the referencing object is getting deleted then there is nothing to clean if (this.allDeletedObjects.contains(referencingObj)) { return; } // get all unidirectional references from referencing obj final EClass eclass = referencingObj.eClass(); final Collection allRefs = eclass.getEAllReferences(); for (final Iterator iter = allRefs.iterator(); iter.hasNext();) { final EReference reference = (EReference)iter.next(); // need to look at unidirectional, non-containment , non-volatile references if (reference.getEOpposite() != null || reference.isVolatile() || !reference.isChangeable() || reference.isContainment()) { continue; } ModelEditor modelEditor = ModelerCore.getModelEditor(); if (reference.isMany()) { final Collection values = (Collection)referencingObj.eGet(reference); for (final Iterator valueIter = values.iterator(); valueIter.hasNext();) { Object value = valueIter.next(); // If this value is is same as the referenced object if (value instanceof EObject && modelEditor.equals((EObject)value, referencedObj)) { // remove the reference to the referenced object remove(referencingObj, reference, value); } } } else { final Object value = referencingObj.eGet(reference); // If this value is is same as the referenced object if (value instanceof EObject && modelEditor.equals((EObject)value, referencedObj)) { // remove the reference to the referenced object unset(referencingObj, reference); } } } } /** * Removes the specified referenced value from the specified referencing object for the specified feature. * * @param theReferencingObject the object referencing another object * @param theFeature the reference feature * @param theValue the feature value which is the referenced object (i.e., the object being deleted) * @since 4.2 */ protected void remove( EObject theReferencingObject, EReference theFeature, Object theValue ) { // only perform delete if none of referencing object's parents are being deleted boolean doDelete = true; EObject parent = theReferencingObject.eContainer(); while ((parent != null) && doDelete) { if (this.allDeletedObjects.contains(parent)) { doDelete = false; } else { parent = parent.eContainer(); } } // Verify that we have not already created a remove command for this feature / value combination. // A volatile feature is one that has no storage directly associated with it. // It's value is generally derived from the values of other features so it // therefore cannot be reset. Fix for defect 12328. if (doDelete && !theFeature.isVolatile() && isRemoveCommandNeeded(theReferencingObject, theFeature, theValue)) { final Command removeCommand = RemoveCommand.create(this.editingDomain, theReferencingObject, theFeature, theValue); this.additionalCommands.add(removeCommand); } } protected void unset( final EObject owner, final EStructuralFeature feature ) { // A volatile feature is one that has no storage directly associated with it. // It's value is generally derived from the values of other features so it // therefore cannot be reset. Fix for defect 12328. if (!this.allDeletedObjects.contains(owner) && !feature.isVolatile() && isUnsetCommandNeeded(owner, feature)) { final Command setCommand = SetCommand.create(this.editingDomain, owner, feature, null); this.additionalCommands.add(setCommand); } } /** * Indicates if the specified feature requires to be unset. Only returns <code>true</code> the first time it is called for the * specified owner/feature pair. So the caller is required to created the command when the return is a <code>true</code>. * Subsequent returns will always return <code>false</code>. * * @param theOwner the owner of the feature * @param theFeature the feature being checked to see if it needs an unset command * @return <code>true</code>if the feature needs to be unset; <code>false</code> otherwise. * @since 4.2 */ private boolean isUnsetCommandNeeded( EObject theOwner, EStructuralFeature theFeature ) { /* * Need to make sure that an unset is executed only once for an object/feature pair. * The problem occurs when the unset is undone. If the opposite feature of the one being set is a multivalued * feature (like primary key to foreign keys), the undo adds the value to the list. If the add is performed * more than once, a duplicate constraint violation occurs. Ref Defect 17011. */ boolean result = false; Set features = (Set)this.objectFeatureUnsetMap.get(theOwner); if (features == null) { features = new HashSet(); this.objectFeatureUnsetMap.put(theOwner, features); } if (!features.contains(theFeature)) { features.add(theFeature); result = true; } return result; } /** * @return */ public List getAdditionalCommands() { return additionalCommands; } }