/***************************************************************************** * Copyright (c) 2010 CEA LIST. * * * 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: * Ansgar Radermacher (CEA LIST) ansgar.radermacher@cea.fr - Initial API and implementation * Vincent Hemery (Atos) - Also take in account modifications for sub-models * *****************************************************************************/ package org.eclipse.papyrus.uml.diagram.common.resourceupdate; import static org.eclipse.papyrus.uml.diagram.common.Activator.log; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.emf.common.command.AbstractCommand; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.edit.domain.EditingDomain; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.papyrus.infra.core.editor.IMultiDiagramEditor; import org.eclipse.papyrus.infra.core.resource.ModelSet; import org.eclipse.papyrus.infra.core.utils.EditorUtils; import org.eclipse.papyrus.infra.services.resourceloading.util.LoadingUtils; import org.eclipse.papyrus.uml.diagram.common.Messages; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorDescriptor; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IPartListener; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; /** * A listener for part activation. Will ask the user whether to reload when he * enters an editor whose underlying resources have changed, used to trigger an * update of * * @author Ansgar Radermacher (CEA LIST) */ public class PartActivationListener implements IPartListener { /** * This class is a simple pair storing a resource delta and whether this delta causes conflict with editor's resource * * @author vhemery */ private static class ResourceModification { private IResourceDelta delta; private boolean conflict; public ResourceModification(IResourceDelta resourceDelta, boolean resourceConflicts) { delta = resourceDelta; conflict = resourceConflicts; } } /** editor to update when activated */ private IMultiDiagramEditor editor; /** paths for modified main resources */ private List<IPath> changedMainResourcePaths; /** modifications performed on all edited resources */ private Map<IPath, ResourceModification> resourceModifications; public PartActivationListener(IMultiDiagramEditor editor) { this.editor = editor; resourceModifications = new HashMap<IPath, ResourceModification>(); changedMainResourcePaths = new ArrayList<IPath>(); } /** * Test if main model has changed * * @return true, when a resource for the underlying editor's main model has been updated * @deprecated use {@link #isMainModelModified()} instead */ @Deprecated public boolean isModied() { return isMainModelModified(); } /** * Test if main model has changed * * @return true, when a resource for the underlying editor's main model has been updated */ public boolean isMainModelModified() { return !changedMainResourcePaths.isEmpty(); } /** * Check if an underlying resource has changed * * @param resourcePathToTest * path of resource to check changes for (including file extension) * @return true, when this resource has been updated for this underlying editor */ public boolean isModified(IPath resourcePathToTest) { return resourceModifications.containsKey(resourcePathToTest); } /** * indicate that the resource for an editor have been modified * * @param changedResourcePath * The path to the resource that has been changed * @param delta * additional information about the change * @deprecated use {@link #setModificationData(IPath, IResourceDelta, boolean)} instead */ @Deprecated public void setModificationData(String changedResourcePath, IResourceDelta delta) { IPath path = Path.fromPortableString(changedResourcePath); setModificationData(path, delta, true, true); } /** * Indicates that a resource for an editor has been modified * * @param changedResourcePath * The path to the resource that has been changed (including file extension) * @param delta * additional information about the change * @param isMainResource * true if resource is part of the main model * @param resourceConflicts * true if the resource contains modifications that should conflict with these changes */ public void setModificationData(IPath changedResourcePath, IResourceDelta delta, boolean isMainResource, boolean resourceConflicts) { if(resourceModifications.containsKey(changedResourcePath)){ //merge two modifications : 1. merge conflicts the pessimistic way resourceModifications.get(changedResourcePath).conflict |= resourceConflicts; //merge two modifications : 2. take latest delta /* * Some delta information is lost, but kind is the best one : * If new delta kind is REMOVED, then ok : resource removed at the end * If new delta kind is ADDED, then there should not be any delta before * If new delta kind is CHANGED, then the action taken for CHANGED is more suitable than others */ resourceModifications.get(changedResourcePath).delta = delta; } resourceModifications.put(changedResourcePath, new ResourceModification(delta, resourceConflicts)); if(isMainResource) { changedMainResourcePaths.add(changedResourcePath); } } public void partActivated(IWorkbenchPart part) { // don't use (part == editor.getSite().getPart()), since different views // (e.g. model explorer or property) // of an active editor may actually be selected IMultiDiagramEditor activeEditor = EditorUtils.getMultiDiagramEditor(); if((editor == activeEditor) && !resourceModifications.isEmpty()) { // the byte union of all kinds of delta met for main resource int mainDeltaKinds = 0; if(!changedMainResourcePaths.isEmpty()) { // look up deltas for the main model for(IPath mainPath : changedMainResourcePaths) { ResourceModification modif = resourceModifications.get(mainPath); mainDeltaKinds |= modif.delta.getKind(); } } // handle change in any resource model (eventually main model or sub-model) int deltaKinds = 0;// the byte union of all kinds of delta met for all resources Set<String> addedModels = new HashSet<String>(); Set<String> removedModels = new HashSet<String>(); // store conflicts in addition for changed models Map<String, Boolean> changedModels = new HashMap<String, Boolean>(); for(Entry<IPath, ResourceModification> entry : resourceModifications.entrySet()) { IPath path = entry.getKey(); ResourceModification modif = entry.getValue(); deltaKinds |= modif.delta.getKind(); switch(modif.delta.getKind()) { case IResourceDelta.ADDED: addedModels.add(path.removeFileExtension().toString()); break; case IResourceDelta.REMOVED: removedModels.add(path.removeFileExtension().toString()); break; case IResourceDelta.CHANGED: String key = path.removeFileExtension().toString(); // do not erase value if a conflicting resource has already been met for this model if(!changedModels.containsKey(key) || !changedModels.get(key)) { changedModels.put(key, modif.conflict); } break; default: break; } } handleDeltaKinds(deltaKinds, addedModels, removedModels, changedModels, mainDeltaKinds); // reinitialize modifications fields changedMainResourcePaths.clear(); resourceModifications.clear(); } } /** * Handle delta kinds with appropriate message and editor action. * * @param deltaKinds * the byte union of met deltas (on all resources) * @param addedModels * the models with added delta (taken in account if (deltaKinds & IResourceDelta.ADDED) > 0) * @param removedModels * the models with removed delta (taken in account if (deltaKinds & IResourceDelta.REMOVED) > 0) * @param changedModels * the models with changed delta (taken in account if (deltaKinds & IResourceDelta.CHANGED) > 0) and whether they conflict with current * editor's changes * @param mainDeltaKinds * the byte union of met deltas (on main resources only) */ private void handleDeltaKinds(int deltaKinds, Set<String> addedModels, Set<String> removedModels, Map<String, Boolean> changedModels, int mainDeltaKinds) { // use masks to check encountered delta types if((deltaKinds & IResourceDelta.ADDED) > 0) { // no particular treatment } if((deltaKinds & IResourceDelta.REMOVED) > 0) { // asynchronous notification to avoid that the removal of // multiple resource files // belonging to the editor (e.g. .uml and .notation) at the same // time leads to multiple // user feedback. String msg = ""; //$NON-NLS-1$ String list = getModelsListString(removedModels); if(removedModels.size() == 1) { msg = String.format(Messages.PartActivationListener_RemovedMsg_single, list); } else { msg = String.format(Messages.PartActivationListener_RemovedMsg_many, list); } MessageDialog.openInformation(new Shell(), Messages.PartActivationListener_RemovedTitle, msg); } if((deltaKinds & IResourceDelta.CHANGED) > 0) { // reopen the editor asynchronously to avoid that changes of // multiple resource files // belonging to the editor (e.g. .uml and .notation) lead to // multiple reloads. // de-activate until user responds to message dialog boolean mainChanged = (mainDeltaKinds & IResourceDelta.CHANGED) > 0; String msg = ""; //$NON-NLS-1$ String list = getModelsListString(changedModels.keySet()); if(changedModels.size() == 1 && mainChanged) { msg = String.format(Messages.PartActivationListener_ChangedMainMsg_single, list); } else if(mainChanged) { msg = String.format(Messages.PartActivationListener_ChangedMainMsg_many, list); } else if(changedModels.size() == 1) { msg = String.format(Messages.PartActivationListener_ChangedMsg_single, list); } else { msg = String.format(Messages.PartActivationListener_ChangedMsg_many, list); } if(editor.isDirty() && mainChanged) { msg += System.getProperty("line.separator"); //$NON-NLS-1$ msg += System.getProperty("line.separator"); //$NON-NLS-1$ msg += Messages.PartActivationListener_ChangedMainWarning; } else if(editor.isDirty()) { // select conflicting models only HashSet<String> dirtyModels = new HashSet<String>(changedModels.size()); for(Entry<String, Boolean> entry : changedModels.entrySet()) { if(entry.getValue()) { dirtyModels.add(entry.getKey()); } } if(!dirtyModels.isEmpty()) { String dirtyList = getModelsListString(dirtyModels); msg += System.getProperty("line.separator"); //$NON-NLS-1$ msg += System.getProperty("line.separator"); //$NON-NLS-1$ msg += String.format(Messages.PartActivationListener_ChangedWarning, dirtyList); } } if(MessageDialog.openQuestion(new Shell(), Messages.PartActivationListener_ChangedTitle, msg)) { if(mainChanged) { // unloading and reloading all resources of the main causes // the following problems // - since resources are removed during the modelSets unload // operation, the call eResource().getContents () // used by the model explorer leads to a null pointer // exception // - diagrams in model explorer are not shown // - would need to reset dirty flags // => clean & simple option is to close and reopen the // editor. Display.getCurrent().asyncExec(new Runnable() { public void run() { IWorkbench wb = PlatformUI.getWorkbench(); IWorkbenchPage page = wb.getActiveWorkbenchWindow().getActivePage(); IEditorInput input = editor.getEditorInput(); page.closeEditor(editor, false); try { IEditorDescriptor desc = wb.getEditorRegistry().getDefaultEditor(input.getName()); page.openEditor(input, desc.getId(), false); } catch (PartInitException e) { log.error(e); } } }); } else { // sub models can be reloaded on their own without reloading the whole model Object dom = editor.getAdapter(EditingDomain.class); if(dom instanceof EditingDomain) { Command refreshCmd = getRefreshCommand((EditingDomain)dom, changedModels.keySet()); if(refreshCmd.canExecute()) { ((EditingDomain)dom).getCommandStack().execute(refreshCmd); } } } } } } /** * Get the command to refresh the changed models * * @param domain * editing domain * @param changedModels * the models to refresh * @return the command to refresh all changed models */ private Command getRefreshCommand(final EditingDomain domain, final Set<String> changedModels) { Command refreshCmd = new AbstractCommand() { /** Model set in which to refresh models */ private ModelSet modelSet; /** URIs of models to update */ List<URI> urisToUpdate; public void execute() { for(URI uri : urisToUpdate) { LoadingUtils.unloadResourcesFromModelSet(modelSet, uri, false); LoadingUtils.loadResourcesInModelSet(modelSet, uri); } } public void redo() { execute(); } @Override public void undo() { execute(); } @Override protected boolean prepare() { ResourceSet set = domain.getResourceSet(); if(set instanceof ModelSet) { modelSet = (ModelSet)set; urisToUpdate = new ArrayList<URI>(changedModels.size()); for(String pathString : changedModels) { IPath path = Path.fromPortableString(pathString); urisToUpdate.add(URI.createPlatformResourceURI(path.toString(), true)); } return true; } return false; } }; return refreshCmd; } /** * Get formatted string with all models of the set displayed in it, separated with a line separator. * An additional separator is added at the beginning to allow separate display of the list in case there are several elements. * * @param models * set of models path * @return formatted list string */ private String getModelsListString(Set<String> models) { StringBuffer list = new StringBuffer(); Iterator<String> it = models.iterator(); if(models.size() == 1) { return it.next(); } while(it.hasNext()) { String model = it.next(); list.append(System.getProperty("line.separator")); //$NON-NLS-1$ list.append(model); } return list.toString(); } public void partDeactivated(IWorkbenchPart part) { } public void partBroughtToTop(IWorkbenchPart part) { } public void partClosed(IWorkbenchPart part) { } public void partOpened(IWorkbenchPart part) { } }