/*
* 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.ui.favorites;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EventObject;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.edit.provider.INotifyChangedListener;
import org.teiid.core.designer.event.EventObjectListener;
import org.teiid.core.designer.util.CoreArgCheck;
import org.teiid.designer.core.ModelEditor;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.core.notification.util.NotificationUtilities;
import org.teiid.designer.core.transaction.SourcedNotification;
import org.teiid.designer.core.workspace.ModelResource;
import org.teiid.designer.core.workspace.ResourceChangeUtilities;
import org.teiid.designer.ui.IModelerCacheListener;
import org.teiid.designer.ui.ModelerCacheEvent;
import org.teiid.designer.ui.UiConstants;
import org.teiid.designer.ui.event.ModelResourceEvent;
class ModelerCacheEventManager
implements INotifyChangedListener, IResourceChangeListener, IResourceDeltaVisitor, EventObjectListener, UiConstants {
// /////////////////////////////////////////////////////////////////////////////////////////////
// FIELDS
// /////////////////////////////////////////////////////////////////////////////////////////////
/** Collection of registered listeners of cache events. */
private ListenerList listeners;
/** The cache being managed. */
private final EObjectModelerCache cache;
private boolean removeDeltaProcessed = false;
// /////////////////////////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS
// /////////////////////////////////////////////////////////////////////////////////////////////
/**
* @param theCache
* @since 4.2
*/
public ModelerCacheEventManager( EObjectModelerCache theCache ) {
this.cache = theCache;
}
// /////////////////////////////////////////////////////////////////////////////////////////////
// METHODS
// /////////////////////////////////////////////////////////////////////////////////////////////
private ModelEditor getModelEditor() {
return ModelerCore.getModelEditor();
}
/**
* Adds the specified listener to the collection of listeners receiving {@link ModelerCacheEvent}s. Listeners already
* registered will not be added again.
*
* @param theListener the listener being added
* @since 4.2
*/
void addListener( IModelerCacheListener theListener ) {
if (this.listeners == null) {
this.listeners = new ListenerList(ListenerList.IDENTITY);
}
this.listeners.add(theListener);
}
/**
* Notifies all registered {@link IModelerCacheListener}s of the specified event.
*
* @param theEvent the event being processed
* @since 4.2
*/
void fireCacheEvent( final ModelerCacheEvent theEvent ) {
if (this.listeners != null) {
final Object[] cacheListeners = this.listeners.getListeners();
for (int i = 0; i < cacheListeners.length; i++) {
((IModelerCacheListener)cacheListeners[i]).cacheChanged(theEvent);
}
}
}
/**
* Finds the cache items affected by the specified <code>Notification</code>. Basically an item is affected if the
* notification's EObject is an ancestor.
*
* @param theNotification the notification being processed
* @return the affected items or an empty collection
* @since 4.2
*/
private Collection getAffectedItems( Notification theNotification ) {
if (theNotification instanceof SourcedNotification) {
CoreArgCheck.isTrue(false, "Input should not be a SourcedNotification."); //$NON-NLS-1$
}
Collection result = Collections.EMPTY_LIST;
Object obj = NotificationUtilities.getEObject(theNotification);
if (obj != null) {
if (this.cache.contains(obj)) {
if (result.isEmpty()) {
result = new ArrayList();
}
result.add(obj);
}
// now see if any descendants are in the cache
Collection descendants = this.cache.getCachedDescendants((EObject)obj);
if (!descendants.isEmpty()) {
if (result.isEmpty()) {
result = new ArrayList();
}
result.addAll(descendants);
}
}
return result;
}
/**
* Finds the cache items affected by the specified <code>Notification</code>. Basically an item is affected if the
* notification's EObject is an ancestor.
*
* @param theNotification the notification being processed
* @return the affected items or an empty collection
* @since 4.2
*/
public Collection getAffectedItems( ModelResource modelResource ) {
Collection result = Collections.EMPTY_LIST;
if (!this.cache.isEmpty() && modelResource != null) {
result = new ArrayList();
Iterator iter = this.cache.iterator();
ModelResource mr = null;
EObject eObj = null;
while (iter.hasNext()) {
eObj = (EObject)iter.next();
mr = getModelEditor().findModelResource(eObj);
if (modelResource.equals(mr))
result.add(eObj);
}
}
return result;
}
/**
* Finds the items in the cache that are stale and have no <code>ModelResource</code>
*
* @return the stale items or an empty collection.
* @since 4.2
*/
public Collection getStaleItems() {
Collection result = Collections.EMPTY_LIST;
if (!this.cache.isEmpty()) {
result = new ArrayList();
Iterator iter = this.cache.iterator();
ModelResource modelResource = null;
EObject eObj = null;
while (iter.hasNext()) {
eObj = (EObject)iter.next();
modelResource = getModelEditor().findModelResource(eObj);
if (modelResource == null) result.add(eObj);
}
}
return result;
}
/**
* Indicates if the specified <code>EObject</code> has been affected by a resource change event for the specified
* <code>IResource</code>.
*
* @param theEObject the object being checked
* @param theResource the resource that was changed
* @return <code>true</code>if affected; <code>false</code> otherwise.
* @since 4.2
*/
private boolean isAffected( EObject theEObject,
IResource theResource ) {
boolean result = false;
if (!this.cache.isEmpty()) {
ModelResource modelResource = getModelEditor().findModelResource(theEObject);
if (modelResource == null) {
result = true;
} else {
IResource model = modelResource.getResource();
if (model != null) {
int type = theResource.getType();
if ((type == IResource.FILE) && model.equals(theResource)) {
result = true;
} else if (type == IResource.FOLDER) {
if (model.getFullPath().toString().startsWith(theResource.getFullPath().toString())) {
result = true;
}
} else if ((type == IResource.PROJECT) && model.getProject().equals(theResource)) {
result = true;
}
}
}
}
return result;
}
/**
* @see org.eclipse.emf.edit.provider.INotifyChangedListener#notifyChanged(org.eclipse.emf.common.notify.Notification)
* @since 4.2
*/
@Override
public void notifyChanged( Notification theNotification ) {
// don't care about adds
if (!this.cache.isEmpty()) {
if (theNotification instanceof SourcedNotification) {
Collection notifications = ((SourcedNotification)theNotification).getNotifications();
Notification nextNotification = null;
Iterator iter = notifications.iterator();
while (iter.hasNext()) {
nextNotification = (Notification)iter.next();
if (NotificationUtilities.isRemoved(nextNotification)) {
processRemove(nextNotification);
} else if (NotificationUtilities.isChanged(nextNotification)) {
processChange(nextNotification);
}
}
} else {
if (NotificationUtilities.isRemoved(theNotification)) {
processRemove(theNotification);
} else if (NotificationUtilities.isChanged(theNotification)) {
processChange(theNotification);
}
}
}
}
/**
* Sends out the appropriate events for a change.
*
* @param theNotification the notification being processed
* @since 4.2
*/
private void processChange( Notification theNotification ) {
Collection changedObjs = null;
ModelerCacheEvent event = null;
if (theNotification instanceof SourcedNotification) {
Iterator itr = ((SourcedNotification)theNotification).getNotifications().iterator();
while (itr.hasNext()) {
Collection temp = getAffectedItems((Notification)itr.next());
if (!temp.isEmpty()) {
if (changedObjs == null) {
changedObjs = new HashSet();
}
changedObjs.addAll(temp);
}
}
} else {
Collection temp = getAffectedItems(theNotification);
if (!temp.isEmpty()) {
changedObjs = new HashSet();
changedObjs.addAll(temp);
}
}
if (changedObjs != null) {
Object obj = (changedObjs.size() == 1) ? changedObjs.iterator().next() : changedObjs;
event = new ModelerCacheEvent(ModelerCacheEvent.CHANGE, obj);
fireCacheEvent(event);
}
}
/**
* Sends out the appropriate events for a remove of an <code>EObject</code>.
*
* @param theNotification the notification being processed
* @since 4.2
*/
private void processRemove( Notification theNotification ) {
Collection removedObjs = null;
ModelerCacheEvent event = null;
if (theNotification instanceof SourcedNotification) {
Iterator itr = ((SourcedNotification)theNotification).getNotifications().iterator();
while (itr.hasNext()) {
Collection temp = removeAffectedItems((Notification)itr.next());
if (!temp.isEmpty()) {
if (removedObjs == null) {
removedObjs = new HashSet();
}
// add to collection for event
removedObjs.addAll(temp);
}
}
} else {
Collection temp = removeAffectedItems(theNotification);
if (!temp.isEmpty()) {
removedObjs = new HashSet();
removedObjs.addAll(temp);
}
}
if (removedObjs != null) {
Object obj = (removedObjs.size() == 1) ? removedObjs.iterator().next() : removedObjs;
event = new ModelerCacheEvent(ModelerCacheEvent.REMOVE, obj);
fireCacheEvent(event);
}
}
/**
* Removes the cache items affected by the specified <code>IResource</code>. Basically an item is affected if the
* <code>IResource</code> is an ancestor.
*
* @param IResource the resource who is a potential ancestor
* @return the r items or an empty collection
* @since 4.2
*/
private Collection removeAffectedItems( IResource theResource ) {
List result = Collections.EMPTY_LIST;
Iterator itr = this.cache.iterator();
while (itr.hasNext()) {
EObject obj = (EObject)itr.next();
if (isAffected(obj, theResource)) {
if (result.isEmpty()) {
result = new ArrayList();
}
// add to result
result.add(obj);
// remove from cache
itr.remove();
}
}
return result;
}
/**
* Finds the cache items affected by the specified <code>Notification</code>. Basically an item is affected if the
* notification's EObject is an ancestor.
*
* @param theNotification the notification being processed
* @return the affected items or an empty collection
* @since 4.2
*/
private Collection removeAffectedItems( Notification theNotification ) {
if (theNotification instanceof SourcedNotification) {
CoreArgCheck.isTrue(false, "Input should not be a SourcedNotification."); //$NON-NLS-1$
}
Collection result = Collections.EMPTY_LIST;
EObject[] kids = NotificationUtilities.getRemovedChildren(theNotification);
for (int i = 0; i < kids.length; i++) {
if (this.cache.contains(kids[i])) {
if (result.isEmpty()) {
result = new ArrayList();
}
// add to result
result.add(kids[i]);
// remove from cache
this.cache.remove(kids[i]);
}
// now see if any descendants are in the cache
Collection descendants = this.cache.getCachedDescendants(kids[i]);
if (!descendants.isEmpty()) {
if (result.isEmpty()) {
result = new ArrayList();
}
// add to result
result.addAll(descendants);
// remove from cache
this.cache.removeAll(descendants);
}
}
return result;
}
/**
* Removes the specified listener from the collection of listeners receiving {@link ModelerCacheEvent}s.
*
* @param theListener the listener being removed
* @since 4.2
*/
void removeListener( IModelerCacheListener theListener ) {
if (this.listeners != null) {
this.listeners.remove(theListener);
if (this.listeners.isEmpty()) {
this.listeners = null;
}
}
}
/**
* @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
* @since 4.2
*/
@Override
public void resourceChanged( IResourceChangeEvent theEvent ) {
if (!this.cache.isEmpty()) {
IResource resource = theEvent.getResource();
// don't process if resource not in model project
if ((resource != null) && !ModelerCore.hasModelNature(resource.getProject())) {
return;
}
if (ResourceChangeUtilities.isProjectClosing(theEvent)) {
// project closing
Collection removedObjs = removeAffectedItems(resource);
// send REMOVE event if necessary
if (!removedObjs.isEmpty()) {
Object obj = (removedObjs.size() > 1) ? removedObjs : removedObjs.iterator().next();
fireCacheEvent(new ModelerCacheEvent(ModelerCacheEvent.REMOVE, obj));
}
} else if (ResourceChangeUtilities.isProjectRenamed(theEvent)) {
// we don't currently allow projects to be renamed but added just in case in the future we do
Collection changedObjs = removeAffectedItems(resource);
// send CHANGE event if necessary
if (!changedObjs.isEmpty()) {
Object obj = (changedObjs.size() > 1) ? changedObjs : changedObjs.iterator().next();
fireCacheEvent(new ModelerCacheEvent(ModelerCacheEvent.CHANGE, obj));
}
} else if (ResourceChangeUtilities.isPreDelete(theEvent)) {
// project being deleted
IProject project = (IProject)theEvent.getResource();
if (project.isOpen()) {
validateContents();
}
} else {
if (theEvent.getType() == IResourceChangeEvent.POST_CHANGE) {
removeDeltaProcessed = false;
IResourceDelta delta = theEvent.getDelta();
if ((delta != null) && ResourceChangeUtilities.isRename(theEvent, delta.getAffectedChildren())) {
// isRename does not work
} else {
// not a rename so visit
try {
theEvent.getDelta().accept(this);
} catch (CoreException theException) {
Util.log(theException);
}
}
}
}
}
}
/**
* Iterates over the cache removing items that no longer exist. One event is fired containing all objects removed.
*
* @since 4.2
*/
private void validateContents() {
List removedObjs = null;
Iterator itr = this.cache.iterator();
while (itr.hasNext()) {
EObject obj = (EObject)itr.next();
if (obj.eResource() == null) {
if (removedObjs == null) {
removedObjs = new ArrayList();
}
// add to objects in event
removedObjs.add(obj);
// remove from cache
itr.remove();
}
}
// send REMOVE event if necessary
if (removedObjs != null) {
Object obj = (removedObjs.size() > 1) ? removedObjs : removedObjs.get(0);
fireCacheEvent(new ModelerCacheEvent(ModelerCacheEvent.REMOVE, obj));
}
}
/**
* @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta)
* @since 4.2
*/
@Override
public boolean visit( IResourceDelta theDelta ) {
boolean result = true;
// System.out.println("VISIT:resource="+theDelta.getResource()+", added="+ResourceChangeUtilities.isAdded(theDelta)+", removed="+ResourceChangeUtilities.isRemoved(theDelta)+", changed="+ResourceChangeUtilities.isChanged(theDelta)+", content changed="+ResourceChangeUtilities.isContentChanged(theDelta)+", repaced="+
// ResourceChangeUtilities.isReplaced(theDelta)+", moved from="+
// ResourceChangeUtilities.isMovedFrom(theDelta)+", moved to="+ ResourceChangeUtilities.isMovedTo(theDelta));
// if a remove and not a rename
if (ResourceChangeUtilities.isRemoved(theDelta) && !ResourceChangeUtilities.isMovedTo(theDelta)
&& !this.removeDeltaProcessed) {
// just process first remove delta
this.removeDeltaProcessed = true;
validateContents();
// don't visit children
result = false;
} else if (ResourceChangeUtilities.isMovedFrom(theDelta)) {
// this is the newly renamed resource. find it's descendants
Collection objs = removeAffectedItems(theDelta.getResource());
if (!objs.isEmpty()) {
// should be a CHANGE event but defect in metadata causes the EObject model resource to be null in
// getAffectedItems(IResource)
// fireCacheEvent(new ModelerCacheEvent(ModelerCacheEvent.CHANGE, objs));
fireCacheEvent(new ModelerCacheEvent(ModelerCacheEvent.REMOVE, objs));
}
}
// check children recursively
return result;
}
/**
* @see org.teiid.core.designer.event.EventObjectListener#processEvent(java.util.EventObject)
* @since 4.2
*/
@Override
public void processEvent( EventObject obj ) {
ModelResourceEvent event = (ModelResourceEvent)obj;
if (event.getType() == ModelResourceEvent.RELOADED) {
// we need to remove all objects in the cache that don't have a model resource anymore or who's model
// resource is the one on the event.
ModelResource changedResource = event.getModelResource();
// Collection objs = getStaleItems();
//
// if (!objs.isEmpty()) {
// this.cache.removeAll(objs);
// }
Collection changedObjs = removeAffectedItems(changedResource.getResource());
if (!changedObjs.isEmpty()) {
fireCacheEvent(new ModelerCacheEvent(ModelerCacheEvent.REMOVE, changedObjs));
}
}
}
}