blob: 7ffb2c3b510131b4a43f5a37a8fd83eddc727259 [file] [log] [blame]
/**
* Copyright (c) 2009-2010 Thales Corporate Services S.A.S.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Thales Corporate Services S.A.S - initial API and implementation
*/
package org.eclipse.egf.core.domain;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.commands.operations.ObjectUndoContext;
import org.eclipse.egf.core.EGFCorePlugin;
import org.eclipse.egf.core.fcore.IPlatformFcore;
import org.eclipse.egf.core.platform.EGFPlatformPlugin;
import org.eclipse.egf.core.platform.pde.IPlatformExtensionPointDelta;
import org.eclipse.egf.core.platform.pde.IPlatformExtensionPointListener;
import org.eclipse.egf.core.workspace.EGFWorkspaceSynchronizer;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.osgi.util.NLS;
/**
* @author Thomas Guiu
*
*/
public final class EGFResourceLoadedListener implements EGFWorkspaceSynchronizer.Delegate {
public static final ResourceEventManager RESOURCE_MANAGER = new ResourceEventManager();
public static interface ResourceUser {
public Resource getResource();
public ResourceListener getListener();
public boolean isDirty();
public boolean userHasSavedResource();
public boolean resourceHasBeenExternallyChanged();
public IOperationHistory getOperationHistory();
public ObjectUndoContext getUndoContext();
}
public static interface ResourceListener {
public void resourceDeleted(Resource resource);
public void resourceMoved(Resource resource, URI oldURI);
public void resourceReloaded(Resource resource);
public void externalUpdate(Resource resource);
public void internalUpdate(Resource resource);
}
public static class ResourceEventManager {
final List<ResourceListener> _listeners = new ArrayList<ResourceListener>();
final Map<Resource, List<ResourceUser>> _observers = new HashMap<Resource, List<ResourceUser>>();
// Due to PDE bug notification we need to handle
// notification in a weird way
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=267954
// See PlatformManager for more informations
final Map<Resource, IPlatformFcore> _fcores = new HashMap<Resource, IPlatformFcore>();
public void addObserver(ResourceUser resourceUser) {
Resource resource = resourceUser.getResource();
List<ResourceUser> list = _observers.get(resource);
if (list == null) {
list = new ArrayList<ResourceUser>();
_observers.put(resource, list);
// Start Workaround PDE Bug 267954
IPlatformFcore fcore = EGFCorePlugin.getPlatformFcore(resource);
if (fcore != null) {
_fcores.put(resource, fcore);
}
// End Workaround PDE Bug 267954
}
list.add(resourceUser);
_listeners.add(resourceUser.getListener());
}
public void removeObserver(ResourceUser resourceUser) {
Resource resource = resourceUser.getResource();
List<ResourceUser> list = _observers.get(resource);
if (list == null) {
return;
}
list.remove(resourceUser);
if (list.isEmpty()) {
try {
resource.unload();
_observers.remove(resource);
// Start Workaround PDE Bug 267954
_fcores.remove(resource);
// End Workaround PDE Bug 267954
if (noMoreObserver() == false) {
resource.load(Collections.EMPTY_MAP);
}
} catch (IOException ioe) {
resource.getErrors().add(new DiagnosticResourceException(resource, ioe));
}
}
_listeners.remove(resourceUser.getListener());
if (noMoreObserver()) {
clearResourceSet();
}
}
private void clearResourceSet() {
// no editor is actually open, so let's unload all the resources
final TransactionalEditingDomain editingDomain = TransactionalEditingDomain.Registry.INSTANCE.getEditingDomain(EGFCorePlugin.EDITING_DOMAIN_ID);
try {
editingDomain.runExclusive(new Runnable() {
public void run() {
for (Iterator<Resource> it = editingDomain.getResourceSet().getResources().iterator(); it.hasNext();) {
Resource resource = it.next();
resource.unload();
it.remove();
}
}
});
if (EGFCorePlugin.getDefault().isDebugging()) {
EGFPlatformPlugin.getDefault().logInfo("EGFResourceLoadedListener.clearResourceSet()"); //$NON-NLS-1$
}
} catch (InterruptedException e) {
EGFCorePlugin.getDefault().logError(e);
}
_fcores.clear();
}
private boolean noMoreObserver() {
for (List<ResourceUser> users : _observers.values()) {
if (users.isEmpty() == false) {
return false;
}
}
return true;
}
public void removeResource(EditingDomain editingDomain, Resource resource) {
if (resource == null) {
throw new IllegalArgumentException();
}
boolean isDirty = false;
// Check whether or not we are editing the current resource
List<ResourceUser> users = _observers.get(resource);
if (users != null) {
for (ResourceUser user : users) {
// This state is propagated, the first user is enough to check this state
isDirty = user.isDirty();
break;
}
}
// Notify, use an iterator as a closing editor always remove its listener
// This we avoid any concurrent modification exception
for (Iterator<ResourceListener> iterator = _listeners.iterator(); iterator.hasNext();) {
ResourceListener resourceListener = iterator.next();
resourceListener.resourceDeleted(resource);
}
// Non dirty editors should close themselves while editing a deleted resource if any
if (isDirty == false) {
resource.unload();
// Start Workaround PDE Bug 267954
_fcores.remove(resource);
// End Workaround PDE Bug 267954
}
}
public void reloadResource(Resource resource) {
if (resource == null) {
throw new IllegalArgumentException();
}
try {
resource.unload();
resource.load(Collections.EMPTY_MAP);
} catch (IOException ioe) {
resource.getErrors().add(new DiagnosticResourceException(resource, ioe));
}
for (Iterator<ResourceListener> iterator = _listeners.iterator(); iterator.hasNext();) {
ResourceListener resourceListener = iterator.next();
resourceListener.resourceReloaded(resource);
}
}
public void movedResource(TransactionalEditingDomain editingDomain, Resource movedResource, URI newURI) {
if (movedResource == null) {
throw new IllegalArgumentException();
}
URI oldURI = movedResource.getURI();
Resource resource = movedResource.getResourceSet().getResource(newURI, false);
// Resource who can't open a physical resource raise exception but are loaded
// in the resource set, its flag is also set to isLoaded
// we need to unload otherwise our resource set will be messy (two resources with the same URI)
if (resource != null && resource.getContents().size() == 0 && resource.getErrors().isEmpty() == false) {
resource.unload();
resource.getResourceSet().getResources().remove(resource);
if (EGFCorePlugin.getDefault().isDebugging()) {
EGFPlatformPlugin.getDefault().logInfo(NLS.bind("EGFResourceLoadedListener.movedResource(...) - discard loaded empty resource with errors ''{0}''", resource.getURI())); //$NON-NLS-1$
}
_fcores.remove(resource);
// Load it in our resource set
movedResource = editingDomain.getResourceSet().getResource(newURI, true);
}
_fcores.remove(movedResource);
movedResource.setURI(newURI);
for (Iterator<ResourceListener> iterator = _listeners.iterator(); iterator.hasNext();) {
ResourceListener resourceListener = iterator.next();
resourceListener.resourceMoved(movedResource, oldURI);
}
}
public boolean resourceHasBeenExternallyChanged(Resource resource) {
if (resource == null) {
throw new IllegalArgumentException();
}
List<ResourceUser> users = _observers.get(resource);
if (users == null) {
return false;
}
boolean resourceHasBeenExternallyChanged = false;
// This state is propagated, the first user is enough to check this state
for (ResourceUser user : users) {
resourceHasBeenExternallyChanged = user.resourceHasBeenExternallyChanged();
break;
}
return resourceHasBeenExternallyChanged;
}
public void populateUndoContext(IOperationHistory operationHistory, ObjectUndoContext undoContext, Resource resource) {
if (resource == null || undoContext == null) {
throw new IllegalArgumentException();
}
List<ResourceUser> users = _observers.get(resource);
if (users == null) {
return;
}
// Operation History is propagated, the first user is enough to retrieve it
ObjectUndoContext innerUndoContext = null;
for (ResourceUser user : users) {
if (user.getUndoContext() != undoContext) {
innerUndoContext = user.getUndoContext();
break;
}
}
// Populate
if (innerUndoContext != null) {
for (IUndoableOperation operation : operationHistory.getUndoHistory(innerUndoContext)) {
operation.addContext(undoContext);
}
for (IUndoableOperation operation : operationHistory.getRedoHistory(innerUndoContext)) {
operation.addContext(undoContext);
}
}
}
}
/**
* This listens for platform changes.
*/
protected IPlatformExtensionPointListener _platformListener = new IPlatformExtensionPointListener() {
public void platformExtensionPointChanged(IPlatformExtensionPointDelta delta) {
TransactionalEditingDomain editingDomain = TransactionalEditingDomain.Registry.INSTANCE.getEditingDomain(EGFCorePlugin.EDITING_DOMAIN_ID);
List<Resource> deltaChangedFcores = new UniqueEList<Resource>();
Map<Resource, IPlatformFcore> deltaRemovedFcores = new HashMap<Resource, IPlatformFcore>();
// Check if a removed platform fcore is applicable
for (IPlatformFcore fcore : delta.getRemovedPlatformExtensionPoints(IPlatformFcore.class)) {
Resource resource = editingDomain.getResourceSet().getResource(fcore.getURI(), false);
if (resource == null) {
continue;
}
deltaRemovedFcores.put(resource, fcore);
}
// Check if an added platform fcore is applicable
// if a removed platform fcore is also detected it means a changed resource
// eg: changed means target versus workspace fcore
for (IPlatformFcore fcore : delta.getAddedPlatformExtensionPoints(IPlatformFcore.class)) {
Resource resource = editingDomain.getResourceSet().getResource(fcore.getURI(), false);
if (resource == null) {
continue;
}
// Resource who can't open a physical resource raise exception but are loaded
// in the resource set, its flag is also set to isLoaded
// we need to unload it to get a chance to load it again
if (resource.getContents().size() == 0 && resource.getErrors().isEmpty() == false) {
// start substitution removed resource if applicable
IPlatformFcore deletedFcore = deltaRemovedFcores.get(resource);
if (deletedFcore != null) {
deltaRemovedFcores.remove(resource);
}
resource.unload();
resource.getResourceSet().getResources().remove(resource);
if (EGFCorePlugin.getDefault().isDebugging()) {
EGFPlatformPlugin.getDefault().logInfo(NLS.bind("EGFResourceLoadedListener.platformExtensionPointChanged(...) - discard loaded empty resource with errors ''{0}''", resource.getURI())); //$NON-NLS-1$
}
RESOURCE_MANAGER._fcores.remove(resource);
// Load it in our resource set
resource = editingDomain.getResourceSet().getResource(fcore.getURI(), true);
if (resource == null) {
continue;
}
// end substitution removed resource if applicable
if (deletedFcore != null) {
deltaRemovedFcores.put(resource, deletedFcore);
}
}
// Start Workaround PDE Bug 267954
IPlatformFcore deletedFcore = deltaRemovedFcores.get(resource);
if (deltaRemovedFcores.remove(resource) != null) {
if (deletedFcore.equals(fcore) == false) {
deltaChangedFcores.add(resource); // <- this statement is not a workaround
}
}
RESOURCE_MANAGER._fcores.put(resource, fcore);
// End Workaround PDE Bug 267954
}
// Process Removed Fcores
if (deltaRemovedFcores.isEmpty() == false) {
for (Resource resource : deltaRemovedFcores.keySet()) {
RESOURCE_MANAGER.removeResource(editingDomain, resource);
}
}
// Target and workspace update, this is not detected by other listeners
// This is safe to do it here
if (deltaChangedFcores.isEmpty() == false) {
for (Resource resource : deltaChangedFcores) {
RESOURCE_MANAGER.reloadResource(resource);
}
}
}
};
private TransactionalEditingDomain _editingDomain;
public EGFResourceLoadedListener() {
EGFPlatformPlugin.getPlatformManager().addPlatformExtensionPointListener(_platformListener);
}
public TransactionalEditingDomain getEditingDomain() {
if (_editingDomain == null) {
_editingDomain = TransactionalEditingDomain.Registry.INSTANCE.getEditingDomain(EGFCorePlugin.EDITING_DOMAIN_ID);
}
return _editingDomain;
}
public boolean handleResourcePersisted(Resource resource) {
for (Iterator<ResourceListener> iterator = RESOURCE_MANAGER._listeners.iterator(); iterator.hasNext();) {
ResourceListener resourceListener = iterator.next();
resourceListener.internalUpdate(resource);
}
return true;
}
public boolean handleResourceMoved(Resource movedResource, URI newURI) {
// Moved resource are always in non dirty editors
// when moving a dirty resource the platform request to save the resource
// if not saved the resource is not moved
Resource resource = getEditingDomain().getResourceSet().getResource(movedResource.getURI(), false);
if (resource == null) {
resource = getEditingDomain().getResourceSet().getResource(newURI, false);
}
if (resource != null) {
// Notify moved resource
RESOURCE_MANAGER.movedResource(getEditingDomain(), resource, newURI);
}
return true;
}
public boolean handleResourceDeleted(Resource deletedResource) {
IPlatformFcore fcore = EGFCorePlugin.getPlatformFcore(deletedResource);
// Either a non Fcore resource or an already processed deleted fcore from _platformListener
if (fcore == null) {
// _platformListener has been called first, Process workspace removed fcores detected in _platformListener
RESOURCE_MANAGER.removeResource(getEditingDomain(), deletedResource);
}
return true;
}
public boolean handleResourceChanged(final Resource changedResource) {
List<ResourceUser> users = RESOURCE_MANAGER._observers.get(changedResource);
// No one edit this resource, process a standard reload
if (users == null) {
RESOURCE_MANAGER.reloadResource(changedResource);
return true;
}
// Check the state of this edited resource
boolean hasSavedResource = false;
boolean isDirty = false;
for (ResourceUser user : users) {
// We need to deeply analyze users as anyone could have saved this resource if any
hasSavedResource |= user.userHasSavedResource();
// This state is propagated, the first user is enough to check this state
isDirty |= user.isDirty();
}
// Nothing to do, we just reload the resource
if (hasSavedResource == false && isDirty == false) {
RESOURCE_MANAGER.reloadResource(changedResource);
return true;
}
// Dirty resource
if (hasSavedResource == false && isDirty) { // Give a chance to cancel dirty editors while reloading external changed resource
for (Iterator<ResourceListener> iterator = RESOURCE_MANAGER._listeners.iterator(); iterator.hasNext();) {
ResourceListener resourceListener = iterator.next();
resourceListener.externalUpdate(changedResource);
}
return true;
}
// Non dirty resource
for (Iterator<ResourceListener> iterator = RESOURCE_MANAGER._listeners.iterator(); iterator.hasNext();) {
ResourceListener resourceListener = iterator.next();
resourceListener.internalUpdate(changedResource);
}
return true;
}
public void dispose() {
EGFPlatformPlugin.getPlatformManager().removePlatformExtensionPointListener(_platformListener);
}
}