| /** |
| * 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); |
| } |
| |
| } |