/* * 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.util; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.IStatus; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EContentsEList; import org.eclipse.emf.ecore.util.EcoreUtil; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.core.notification.util.NotificationUtilities; import org.teiid.designer.core.resource.EmfResource; import org.teiid.designer.core.workspace.ModelResource; import org.teiid.designer.core.workspace.ModelWorkspaceException; /** * @since 8.0 */ public class ExternalResourceImportsHelper { /** * Method which processes a given notification for model import status. If a notification results in an external reference * being set or added a call to addMissingImports() will be made. If an external resource is detected to be removed, the method * returns a ProcessNotificationResult which stores the removed external resources. * @param theNotification * @return * @since 5.0 */ public static ProcessedNotificationResult processNotification(final Notification theNotification) { Resource resource = null; Object notifier = theNotification.getNotifier(); // mark the resource modified if the notification is for (1) a resource that is not a member of // an external resource set or for (2) an EObject of a resource. if (notifier instanceof Resource) { // make sure the feature is NOT the modified feature resource = (Resource)notifier; int featureId = theNotification.getFeatureID(resource.getClass()); if (theNotification.getEventType() == Notification.REMOVING_ADAPTER || featureId == Resource.RESOURCE__IS_LOADED || featureId == Resource.RESOURCE__IS_MODIFIED || featureId == Resource.RESOURCE__RESOURCE_SET || isExternalResourceSetMember(resource) ) { // DO NOT PROCESS NOTIFICATION. WILL NOT HAVE AN EFFECT ON IMPORTS return null; } } else if (notifier instanceof EObject) { resource = ((EObject)notifier).eResource(); } // Resource may be null here. Deleting a DiagramEntity, for instance, will result in a notification where the notifier // is the DiagramEntity and the "Diagram" reference is being set to NULL, however the DiagramEntity's EResource will // be NULL. Just return and DO NOT PROCESS. if( resource == null ) { return null; } ProcessedNotificationResult result = new ProcessedNotificationResult(resource); boolean wasProcessed = false; if( NotificationUtilities.isAdded(theNotification)) { // For ADD notifications, we just need to walk the added children and check for any external references. This might // happen if you copy/paste a Column with a datatype into a model with columns that have no datatype!! EObject[] newChildren = NotificationUtilities.getAddedChildren(theNotification); boolean importsWereAdded = false; for(int i=0; i < newChildren.length; i++ ) { importsWereAdded = ImportUtilities.addMissingImports(resource, newChildren[i]); if( importsWereAdded ) { result.setImportsWereAdded(true); } } wasProcessed = true; } else if(NotificationUtilities.isRemoved(theNotification)) { // For REMOVE notifications, we need to detect if any external references are included in the deleted objects or any // children of those objects. EObject[] removedChildren = NotificationUtilities.getRemovedChildren(theNotification); Collection externalResources = new HashSet(); // Search for each removed child (recursively) for(int i=0; i < removedChildren.length; i++ ) { externalResources.addAll(findExternalResourceReferences(resource, removedChildren[i])); } wasProcessed = true; // If we find any, we need to make sure rebuildImports is set to TRUE, so the caller knows. if( ! externalResources.isEmpty() ) { // add the resources to the dereferenced list. result.addDereferencedResources(externalResources); } } if( ! wasProcessed ) { switch( theNotification.getEventType()) { case Notification.ADD: case Notification.ADD_MANY: case Notification.MOVE: case Notification.REMOVE: case Notification.REMOVE_MANY: case Notification.REMOVING_ADAPTER: case Notification.RESOLVE: break; case Notification.SET: { // If an EReference feature is being set if( theNotification.getFeature() instanceof EReference ) { Object newValue = theNotification.getNewValue(); Object oldValue = theNotification.getOldValue(); if( newValue != null && oldValue == null ) { if( newValue instanceof EObject && ((EObject)newValue).eResource() != null ) { Resource refResource = ((EObject)newValue).eResource(); boolean importsWereAdded = false; if( resource != refResource && resource instanceof EmfResource) { importsWereAdded = ImportUtilities.addMissingImport(resource, refResource); if( importsWereAdded ) { result.setImportsWereAdded(true); } } } } else if (newValue != oldValue) { // If newValue is NULL, get Old Value and add the resource to the dereferenced list. if( newValue == null && oldValue != null ) { if( oldValue instanceof EObject && ((EObject)oldValue).eResource() != null ) { Resource refResource = ((EObject)oldValue).eResource(); if( refResource != null && resource instanceof EmfResource) { result.addDereferencedResource(refResource); } } } else if(newValue != null && oldValue != null ) { // This is the case where we've swapped out an EReference // Need to both ADD an import for the new EReference (if needed) and also // Put the OLD resource up for removal... if( newValue instanceof EObject && ((EObject)newValue).eResource() != null ) { Resource refResource = ((EObject)newValue).eResource(); boolean importsWereAdded = false; if( resource != refResource && resource instanceof EmfResource) { importsWereAdded = ImportUtilities.addMissingImport(resource, refResource); if( importsWereAdded ) { result.setImportsWereAdded(true); } } } if( oldValue instanceof EObject && ((EObject)oldValue).eResource() != null ) { Resource refResource = ((EObject)oldValue).eResource(); if( refResource != null && resource instanceof EmfResource) { result.addDereferencedResource(refResource); } } } } } } break; case Notification.UNSET: { // Don't know use case?? } break; default: break; } } return result; } /** * Method designed to * @param notificationResultsList * @since 5.0 */ public static void processNotificationResults(Collection notificationResultsList) { // Walk through each result and create a map of changed resources to a Collection of dereferenced external resources ProcessedNotificationResult nextResult = null; for( Iterator iter = notificationResultsList.iterator(); iter.hasNext(); ) { nextResult = (ProcessedNotificationResult)iter.next(); // For the target resource, walk the model with a list of references. Once a reference is // First create a copy of the procesedNotificationResult ProcessedNotificationResult tempResult = new ProcessedNotificationResult(nextResult); checkRemainingExternalReferences(tempResult); // Now if there are ANY REMAINING, we need to remove the imports for these objects for( Iterator iter2 = tempResult.getDereferencedResources().iterator(); iter2.hasNext(); ) { Resource nextRes = (Resource)iter2.next(); ImportUtilities.removeExistingImport(tempResult.getTargetResource(), nextRes); } } } /** * Indicates if the specified <code>Resource</codee> is a member of an external resource set. * @param theResource the resource being checked * @return <code>true</code> if a member; <code>false</code> otherwise. * @since 5.0.2 */ private static boolean isExternalResourceSetMember(Resource theResource) { return ModelerCore.isResourceInExternalResourceSet(theResource); } /** * Find and return a list of external references contain within or below the target provided. * This method looks for both EReference and EReference.isMany() properties. * @param target Object * @return Collection of external referenced EResources * @since 5.0 */ public static Collection findExternalResourceReferences(final Resource notifyingResource, final Object target) { Collection externalResources = new HashSet(); if( target instanceof EObject ) { externalResources = findExternalResourceReferences(notifyingResource, (EObject)target); } else if( target instanceof Resource ) { externalResources = findExternalResourceReferences((Resource)target); } else if( target instanceof ModelResource ) { externalResources = findExternalResourceReferences((ModelResource)target); } return externalResources; } /** * Find and return a list of external references contain within or below the EObject target provided. * This method looks for both EReference and EReference.isMany() properties. * @param target EObject * @return Collection of external referenced EResources * @since 5.0 */ private static Collection findExternalResourceReferences(final Resource notifyingResource, final EObject target) { Resource targetResource = target.eResource(); // resource == null, then it was deleted and we need to use the notifyingResource if( targetResource == null ) { targetResource = notifyingResource; } Collection externalResources = new HashSet(); // Iterate over all the features that are non-containment ... for (Iterator iter = target.eAllContents(); iter.hasNext();) { EObject eObject = (EObject) iter.next(); // Iterate over all the EReferences looking for external resource references for (Iterator iter2 = eObject.eClass().getEAllReferences().iterator(); iter2.hasNext();) { final EReference eReference = (EReference)iter2.next(); if ( !eReference.isContainment() && !eReference.isContainer() ) { // The reference is NOT the container NOR a containment feature ... final Object value = eObject.eGet(eReference,false); if ( eReference.isMany() ) { // There may be many values ... final Iterator valueIter = ((List)value).iterator(); while (valueIter.hasNext()) { final Object valueInList = valueIter.next(); if ( valueInList != null && valueInList instanceof EObject ) { final Resource valueResource = ((EObject)valueInList).eResource(); if (targetResource != valueResource && !externalResources.contains(valueResource)) { externalResources.add(valueResource); } } } } else { // There may be 0..1 value ... if ( value != null && value instanceof EObject ) { EObject eObj = (EObject)value; if( eObj.eIsProxy() ) { eObj = EcoreUtil.resolve(eObj, targetResource.getResourceSet()); } final Resource valueResource = eObj.eResource(); if (valueResource != null && targetResource != valueResource && !externalResources.contains(valueResource)) { externalResources.add(valueResource); } } } } } } // Check cross references for (EContentsEList.FeatureIterator featureIterator = (EContentsEList.FeatureIterator)target.eCrossReferences().iterator(); featureIterator.hasNext(); ) { EObject eObject = (EObject)featureIterator.next(); Resource refResource = eObject.eResource(); externalResources.add(refResource); } return externalResources; } /** * Find and return a list of external references contain within or below the EObject target provided. * This method looks for both EReference and EReference.isMany() properties. * @param target EObject * @return Collection of external referenced EResources * @since 5.0 */ private static void checkRemainingExternalReferences( final Resource notifyingResource, final EObject target, final ProcessedNotificationResult tempResult) { Resource targetResource = target.eResource(); // resource == null, then it was deleted and we need to use the notifyingResource if( targetResource == null ) { targetResource = notifyingResource; } // Iterate over all the features that are non-containment ... for (Iterator iter = target.eAllContents(); iter.hasNext();) { EObject eObject = (EObject) iter.next(); // Iterate over all the EReferences looking for external resource references for (Iterator iter2 = eObject.eClass().getEAllReferences().iterator(); iter2.hasNext();) { final EReference eReference = (EReference)iter2.next(); if ( !eReference.isContainment() && !eReference.isContainer() ) { // The reference is NOT the container NOR a containment feature ... final Object value = eObject.eGet(eReference,false); if ( eReference.isMany() ) { // There may be many values ... final Iterator valueIter = ((List)value).iterator(); while (valueIter.hasNext()) { final Object valueInList = valueIter.next(); if ( valueInList != null && valueInList instanceof EObject ) { final Resource valueResource = ((EObject)valueInList).eResource(); // If this resource is contained in the tempResult, assume that it still needs to be referenced // as an Import, so we can remove it from the tempResult search if( tempResult.getDereferencedResources().contains(valueResource)) { tempResult.removeDereferencedResource(valueResource); } } if( tempResult.getDereferencedResources().isEmpty() ) { // IF there are NO MORE external references to search for, we return, our job is done. return; } } } else { // There may be 0..1 value ... if ( value != null && value instanceof EObject ) { EObject eObj = (EObject)value; if( eObj.eIsProxy() ) { eObj = EcoreUtil.resolve(eObj, targetResource.getResourceSet()); } final Resource valueResource = eObj.eResource(); // If this resource is contained in the tempResult, assume that it still needs to be referenced // as an Import, so we can remove it from the tempResult search if( tempResult.getDereferencedResources().contains(valueResource)) { tempResult.removeDereferencedResource(valueResource); } } } } if( tempResult.getDereferencedResources().isEmpty() ) { // IF there are NO MORE external references to search for, we return, our job is done. return; } } if( tempResult.getDereferencedResources().isEmpty() ) { // IF there are NO MORE external references to search for, we return, our job is done. return; } } // Check cross references for (EContentsEList.FeatureIterator featureIterator = (EContentsEList.FeatureIterator)target.eCrossReferences().iterator(); featureIterator.hasNext(); ) { EObject eObject = (EObject)featureIterator.next(); Resource refResource = eObject.eResource(); if( tempResult.getDereferencedResources().contains(refResource)) { tempResult.removeDereferencedResource(refResource); } if( tempResult.getDereferencedResources().isEmpty() ) { // IF there are NO MORE external references to search for, we return, our job is done. return; } } } private static Collection findExternalResourceReferences(final ModelResource modelResource) { Resource eResource = null; try { eResource = modelResource.getEmfResource(); } catch (ModelWorkspaceException theException) { ModelerCore.Util.log(IStatus.ERROR, theException, theException.getLocalizedMessage()); } if( eResource != null ) { return findExternalResourceReferences(eResource); } return Collections.EMPTY_LIST; } private static Collection findExternalResourceReferences(final Resource resource) { Collection externalResources = new HashSet(); EList modelContents = resource.getContents(); for(Iterator iter = modelContents.iterator(); iter.hasNext(); ) { externalResources.addAll(findExternalResourceReferences(resource, (EObject)iter.next())); } return externalResources; } private static void checkRemainingExternalReferences(final ProcessedNotificationResult tempResult) { Resource resource = tempResult.getTargetResource(); EList modelContents = resource.getContents(); for(Iterator iter = modelContents.iterator(); iter.hasNext(); ) { checkRemainingExternalReferences(resource, (EObject)iter.next(), tempResult); if( tempResult.getDereferencedResources().isEmpty() ) { // IF there are NO MORE external references to search for, we return, our job is done. return; } } } }