/*****************************************************************************
* Copyright (c) 2010 ATOS ORIGIN.
*
* 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:
* Tristan Faure (ATOS ORIGIN INTEGRATION) tristan.faure@atosorigin.com - Initial API and implementation
*****************************************************************************/
package org.eclipse.papyrus.infra.services.resourceloading.strategies;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.papyrus.infra.core.editor.CoreMultiDiagramEditor;
import org.eclipse.papyrus.infra.core.resource.ModelSet;
import org.eclipse.papyrus.infra.core.sasheditor.contentprovider.IPageMngr;
import org.eclipse.papyrus.infra.core.sasheditor.di.contentprovider.DiSashModelMngr;
import org.eclipse.papyrus.infra.core.services.ServiceException;
import org.eclipse.papyrus.infra.services.resourceloading.ILoadingStrategy;
import org.eclipse.papyrus.infra.widgets.toolbox.notification.INotification;
import org.eclipse.papyrus.infra.widgets.toolbox.notification.NotificationRunnable;
import org.eclipse.papyrus.infra.widgets.toolbox.notification.Type;
import org.eclipse.papyrus.infra.widgets.toolbox.notification.builders.IContext;
import org.eclipse.papyrus.infra.widgets.toolbox.notification.builders.NotificationBuilder;
import org.eclipse.swt.SWT;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IPerspectiveDescriptor;
import org.eclipse.ui.IPerspectiveListener;
import org.eclipse.ui.IWindowListener;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
/**
* The strategy which ask user if the resource has to be loaded or not
*
* @author tfaure
*
*/
public class AskUserStrategy implements ILoadingStrategy {
/**
* the uris linked to the uri of an editor already guessed to the user
*/
private Map<URI, Set<URI>> alreadyGuessed = new HashMap<URI, Set<URI>>();
/**
* the uris linked to the uri of an editor already authorized
*/
Map<URI, Set<URI>> authorized = new HashMap<URI, Set<URI>>();
/**
* The extensions needed to load for each resource
*/
Map<URI, Map<URI, Set<String>>> mappingURIExtensions = new HashMap<URI, Map<URI, Set<String>>>();
/**
* Listeners triggered when the editors are closed
*/
Map<URI, EditorCloseListener> listeners = new HashMap<URI, AskUserStrategy.EditorCloseListener>();
/**
* All the notifications created for each editor
*/
Map<URI, List<INotification>> notifications = new HashMap<URI, List<INotification>>();
/**
* Constant URI for Yes For All Option
*/
public static final URI YesForAllURI = URI.createURI(Messages.AskUserStrategy_YES_FOR_ALL_URI);
/**
* Constant URI for No For All Option
*/
private static final URI NoForAllURI = URI.createURI(Messages.AskUserStrategy_NO_FOR_ALL_URI);
/**
* @see org.eclipse.papyrus.infra.services.resourceloading.ILoadingStrategy#loadResource(org.eclipse.papyrus.infra.core.resource.ModelSet,
* org.eclipse.emf.common.util.URI)
*
* @param modelSet
* @param uri
* @return
*/
public boolean loadResource(ModelSet modelSet, URI uri) {
// pathmap resource are always loaded
boolean result = !uri.isPlatform() && !uri.isFile();
URI initialURI = URI.createPlatformResourceURI(modelSet.getFilenameWithoutExtension().toString(), true);
// if no listener is registered, a listener is added on the editor to remove the notifications
// when the editor is closed the listener removes too the choices made by the user.
addClosingEditorListener(initialURI);
// all the verification are made with the uri without the file extension
// each load is performed for a set of resources
URI trimFileExtensionAndFragment = uri.trimFragment().trimFileExtension();
if(!result) {
result = initialURI.equals(trimFileExtensionAndFragment);
}
if(!result) {
manageExtensions(initialURI, uri);
Set<URI> uris = authorized.get(initialURI);
if(uris != null && (uris.contains(YesForAllURI) || uris.contains(trimFileExtensionAndFragment))) {
result = true;
}
if(uris != null && (uris.contains(NoForAllURI))) {
// in this case always false !
return false;
}
if(!result) {
manageGuess(modelSet, initialURI, trimFileExtensionAndFragment);
}
}
return result;
}
/**
* Create the ask notification to the user
*
* @param modelSet
* @param initialURI
* @param trimFragment
*/
protected void manageGuess(ModelSet modelSet, URI initialURI, URI trimFragment) {
if(!alreadyGuessed(initialURI, trimFragment) && !alreadyGuessed(initialURI, YesForAllURI) && !alreadyGuessed(initialURI, NoForAllURI)) {
String message = new StringBuffer(Messages.AskUserStrategy_MESSAGE_PART_1).append(initialURI.lastSegment()).append(Messages.AskUserStrategy_MESSAGE_PART_2).append(trimFragment.toString()).append(Messages.AskUserStrategy_MESSAGE_PART3).toString();
addGuessed(initialURI, trimFragment);
NotificationBuilder builder = getNotification(message, trimFragment, modelSet, initialURI);
INotification notification = builder.run();
addNotification(initialURI, new EncapsulatedNotification(notification, trimFragment));
// notification
}
}
/**
* Add a listener closing the uri parameter
*
* @param initialURI
*/
private void addClosingEditorListener(URI initialURI) {
if(!listeners.containsKey(initialURI)) {
EditorCloseListener value = new EditorCloseListener();
if(addPageListener(value)) {
listeners.put(initialURI, value);
}
}
}
/**
* Remember that an uri has already be asked
*
* @param initialURI
* , the uri opened by the editor
* @param guessed
*/
private void addGuessed(URI initialURI, URI guessed) {
Set<URI> set = alreadyGuessed.get(initialURI);
if(set == null) {
set = new HashSet<URI>();
alreadyGuessed.put(initialURI, set);
}
set.add(guessed);
}
/**
* Check if the uri is already asked
*
* @param initialURI
* , the uri opened by the editor
* @param toLoad
* , the uri to load
* @return
*/
private boolean alreadyGuessed(URI initialURI, URI toLoad) {
Set<URI> guessed = alreadyGuessed.get(initialURI);
if(guessed != null) {
return guessed.contains(toLoad);
}
return false;
}
/**
* The extensions are saved to load all the specified resource if the user
* wants to load a set of resources
*
* @param uri
* , the file to load
* @param trimFragment
*/
private void manageExtensions(URI initialURI, URI toLoad) {
Map<URI, Set<String>> map = mappingURIExtensions.get(initialURI);
if(map == null) {
map = new HashMap<URI, Set<String>>();
mappingURIExtensions.put(initialURI, map);
}
Set<String> extensions = map.get(toLoad.trimFileExtension());
if(extensions == null) {
extensions = new HashSet<String>();
map.put(toLoad.trimFileExtension(), extensions);
}
extensions.add(toLoad.fileExtension());
}
/**
* Get the current editor
*
* @return
*/
protected CoreMultiDiagramEditor getCurrentEditor() {
IEditorPart editor = getEditor();
if(editor instanceof CoreMultiDiagramEditor) {
return (CoreMultiDiagramEditor)editor;
}
return null;
}
/**
* Create the notification with Yes/No/Yes For All/No For All option
*
* @param message
* , the message to display
* @param uri
* , the uri to load
* @param modelSet
* , the model set of the editor
* @param initialURI
* , the uri opened by the editor
* @return a {@link NotificationBuilder} to build
*/
protected NotificationBuilder getNotification(String message, final URI uri, final ModelSet modelSet, final URI initialURI) {
NotificationRunnable yes = getYesRunnable(uri, modelSet, initialURI);
NotificationRunnable no = getNoRunnable(uri, modelSet, initialURI);
NotificationRunnable yesForAll = getYesForAllRunnable(uri, modelSet, initialURI);
NotificationRunnable noForAll = getNoForAllRunnable(uri, modelSet, initialURI);
return new NotificationBuilder().setType(Type.QUESTION).setAsynchronous(true).setTemporary(false).setMessage(message).setHTML(true).setAsynchronous(true).setTitle(Messages.AskUserStrategy_LOAD_RESOURCE + uri.toString()).addAction(yes).addAction(no).addAction(yesForAll).addAction(noForAll);
}
/**
* NO for all registers the NoForAll URI and do nothing
* @param uri
* @param modelSet
* @param initialURI
* @return
*/
protected LoadAndRefreshRunnable getNoForAllRunnable(final URI uri, final ModelSet modelSet, final URI initialURI) {
return new LoadAndRefreshRunnable(initialURI, uri, modelSet, Messages.AskUserStrategy_NO_FOR_ALL) {
@Override
public void run(IContext context) {
addGuessed(getInitialURI(), NoForAllURI);
super.run(context);
}
@Override
protected void manageRefresh(IEditorReference ref, IEditorPart part) {
addAuthorized(getInitialURI(), NoForAllURI);
List<INotification> list = notifications.get(getInitialURI());
if(list != null) {
for(INotification n : notifications.get(getInitialURI())) {
if(n != getNotification()) {
n.delete();
}
}
}
// DO NOTHING
}
@Override
public int getActionValue() {
return SWT.NO;
}
};
}
/**
* Yes for All registers Yes For All URI and refresh the tabs to take in account the loaded elements
* @param uri
* @param modelSet
* @param initialURI
* @return
*/
protected LoadAndRefreshRunnable getYesForAllRunnable(final URI uri, final ModelSet modelSet, final URI initialURI) {
return new LoadAndRefreshRunnable(initialURI, uri, modelSet, Messages.AskUserStrategy_0) {
@Override
public void run(IContext context) {
addGuessed(getInitialURI(), YesForAllURI);
super.run(context);
}
@Override
RefreshRunnable getRunnable() {
return new RefreshRunnable(modelSet, uri, initialURI, true, true) {
@Override
public void run(CoreMultiDiagramEditor editor) {
List<INotification> list = notifications.get(getInitialURI());
if(list != null) {
for(INotification n : notifications.get(getInitialURI())) {
if(n instanceof EncapsulatedNotification) {
EncapsulatedNotification encapsulated = (EncapsulatedNotification)n;
new RefreshRunnable(modelSet, encapsulated.getURIToLoad(), getInitialURI(), false, false).run(editor);
}
}
}
new RefreshRunnable(modelSet, null, getInitialURI(), true, true).run(editor);
};
};
}
@Override
protected void manageRefresh(IEditorReference ref, IEditorPart part) {
addAuthorized(getInitialURI(), YesForAllURI);
INotification currentNotification = getNotification();
List<INotification> list = notifications.get(getInitialURI());
if(list != null) {
for(INotification n : notifications.get(getInitialURI())) {
if(n != currentNotification) {
n.delete();
}
}
}
super.manageRefresh(ref, part);
}
@Override
public int getActionValue() {
return SWT.YES;
}
};
}
/**
* No runnable does not accept the URI asked
* @param uri
* @param modelSet
* @param initialURI
* @return
*/
protected LoadAndRefreshRunnable getNoRunnable(final URI uri, final ModelSet modelSet, final URI initialURI) {
return new LoadAndRefreshRunnable(initialURI, uri, modelSet, Messages.AskUserStrategy_NO) {
@Override
protected void manageRefresh(IEditorReference ref, IEditorPart part) {
// DO NOTHING
// inheritance for close listener
}
@Override
public int getActionValue() {
return SWT.NO;
}
};
}
/**
* Yes registers the URI and refresh the tab to take in account the loaded elements
* @param uri
* @param modelSet
* @param initialURI
* @return
*/
protected LoadAndRefreshRunnable getYesRunnable(final URI uri, final ModelSet modelSet, final URI initialURI) {
return new LoadAndRefreshRunnable(initialURI, uri, modelSet, Messages.AskUserStrategy_YES);
}
/**
* Register the notification created to the URI open by the editor
* @param initialURI
* @param notification
*/
protected void addNotification(URI initialURI, INotification notification) {
List<INotification> notifs = notifications.get(initialURI);
if(notifs == null) {
notifs = new LinkedList<INotification>();
notifications.put(initialURI, notifs);
}
notifs.add(notification);
}
protected IEditorPart getEditor() {
IWorkbenchWindow activeWorkbenchWindow = getActiveWorkbenchWindow();
if(activeWorkbenchWindow != null) {
IWorkbenchPage activePage = activeWorkbenchWindow.getActivePage();
if(activePage != null) {
return activePage.getActiveEditor();
}
}
return null;
}
protected IWorkbenchWindow getActiveWorkbenchWindow() {
IWorkbench workbench = PlatformUI.getWorkbench();
if(workbench != null) {
return workbench.getActiveWorkbenchWindow();
}
return null;
}
protected IWorkbenchPage getActivePage() {
IWorkbenchWindow activeWorkbenchWindow = getActiveWorkbenchWindow();
if(activeWorkbenchWindow != null) {
return activeWorkbenchWindow.getActivePage();
}
return null;
}
/**
* A basic runnable managing the load of a resource and a refresh of the tabs
* If the editor is not opened or activated some listeners will be triggered to launch the refresh
*
* @author tfaure
*
*/
private class LoadAndRefreshRunnable implements NotificationRunnable {
protected URI initialURI;
protected URI uri;
protected ModelSet modelSet;
protected final String label;
protected IContext theContext;
public LoadAndRefreshRunnable(URI initialURI, URI uri, ModelSet modelSet, String label) {
this(initialURI, uri, modelSet, label, false);
}
public LoadAndRefreshRunnable(URI initialURI, URI uri, ModelSet modelSet, String label, boolean refreshAll) {
this.initialURI = initialURI;
this.uri = uri;
this.modelSet = modelSet;
this.label = label;
}
public URI getInitialURI() {
return initialURI;
}
public void run(IContext context) {
context.put(IContext.ACTION_ID, getActionValue());
theContext = context;
IWorkbenchPage activePage = getActivePage();
if(activePage != null) {
// search the editor which opened the given URI
for(IEditorReference ref : activePage.getEditorReferences()) {
try {
IFile file = (IFile)ref.getEditorInput().getAdapter(IFile.class);
if(file != null) {
URI createPlatformResourceURI = URI.createPlatformResourceURI(file.getFullPath().removeFileExtension().toString(), true);
if(createPlatformResourceURI != null && createPlatformResourceURI.equals(initialURI)) {
IEditorPart part = (IEditorPart)ref.getPart(false);
manageRefresh(ref, part);
// add the uris to load in authorized list
}
}
} catch (PartInitException e) {
e.printStackTrace();
}
}
} else {
PlatformUI.getWorkbench().addWindowListener(new IWindowListener() {
public void windowOpened(IWorkbenchWindow window) {
}
public void windowDeactivated(IWorkbenchWindow window) {
}
public void windowClosed(IWorkbenchWindow window) {
}
public void windowActivated(IWorkbenchWindow window) {
if(window.getActivePage() != null) {
run(new IContext.Context());
PlatformUI.getWorkbench().removeWindowListener(this);
}
}
});
}
}
public int getActionValue() {
return SWT.YES;
}
public INotification getNotification() {
return new EncapsulatedNotification((INotification)theContext.get(IContext.NOTIFICATION_OBJECT), uri);
}
protected void manageRefresh(IEditorReference ref, IEditorPart part) {
addAuthorized(initialURI, uri);
if(part != null) {
getRunnable().run((CoreMultiDiagramEditor)part);
} else {
addPageListener(new EditorActivateListener(ref, modelSet, uri, initialURI, getRunnable()));
}
}
RefreshRunnable getRunnable() {
return new RefreshRunnable(modelSet, uri, initialURI, false, true);
}
public String getLabel() {
return label;
}
}
/**
* Add a page listener
* @param listener
* @return
*/
protected boolean addPageListener(IPartListener listener) {
IWorkbenchPage page = getActivePage();
if(page != null) {
page.addPartListener(listener);
return true;
}
return false;
}
/**
* Register a listener of the perspective to know when the editor is finally closed
* @param listener
* @return
*/
protected boolean addPerspectiveListener(IPerspectiveListener listener) {
if(PlatformUI.getWorkbench() != null && PlatformUI.getWorkbench().getActiveWorkbenchWindow() != null) {
PlatformUI.getWorkbench().getActiveWorkbenchWindow().addPerspectiveListener(listener);
return true;
}
return false;
}
/**
* Unregister a perspective listener
* @param listener
* @return
*/
protected void removePerspectiveListener(IPerspectiveListener listener) {
if(PlatformUI.getWorkbench() != null && PlatformUI.getWorkbench().getActiveWorkbenchWindow() != null) {
PlatformUI.getWorkbench().getActiveWorkbenchWindow().removePerspectiveListener(listener);
}
}
protected void removePageListener(IPartListener listener) {
IWorkbenchPage page = getActivePage();
if(page != null) {
page.removePartListener(listener);
}
}
protected void addAuthorized(URI initial, URI toAutorized) {
Set<URI> uris = authorized.get(initial);
if(uris == null) {
uris = new HashSet<URI>();
authorized.put(initial, uris);
}
uris.add(toAutorized);
}
/**
* Notification containing the {@link URI} loaded
*
*/
public class EncapsulatedNotification implements INotification {
private final INotification notification;
private final URI uri;
public EncapsulatedNotification(INotification notification, URI uri) {
this.notification = notification;
this.uri = uri;
}
public void delete() {
this.notification.delete();
}
public boolean isDeleted() {
return this.notification.isDeleted();
}
public URI getURIToLoad() {
return uri;
}
}
/**
* An empty {@link IPartListener} implementation
* @author tfaure
*
*/
private class EditorAdapter implements IPartListener {
public void partActivated(IWorkbenchPart part) {
}
public void partBroughtToTop(IWorkbenchPart part) {
}
public void partClosed(IWorkbenchPart part) {
}
public void partDeactivated(IWorkbenchPart part) {
}
public void partOpened(IWorkbenchPart part) {
}
}
/**
* Listener which is notifid when an editor is activated
* @author tfaure
*
*/
private class EditorActivateListener extends EditorAdapter {
private final IEditorReference reference;
private final RefreshRunnable refreshRunnable;
public EditorActivateListener(IEditorReference reference, ModelSet set, URI uri, URI initialURI, RefreshRunnable refreshRunnable) {
this.reference = reference;
this.refreshRunnable = refreshRunnable;
}
@Override
public void partActivated(IWorkbenchPart part) {
if(reference.getPart(false) == part && part instanceof CoreMultiDiagramEditor) {
refreshRunnable.run((CoreMultiDiagramEditor)part);
removePageListener(this);
}
}
}
/**
* Listener which is notifid when an editor is closed
* @author tfaure
*
*/
private class EditorCloseListener extends EditorAdapter {
@Override
public void partClosed(IWorkbenchPart part) {
super.partClosed(part);
if(part instanceof CoreMultiDiagramEditor) {
CoreMultiDiagramEditor editor = (CoreMultiDiagramEditor)part;
IFile file = (IFile)editor.getEditorInput().getAdapter(IFile.class);
if(file != null) {
if(file.getFullPath() != null) {
final URI uri = URI.createPlatformResourceURI(file.getFullPath().removeFileExtension().toString(), true);
addPerspectiveListener(new EditorClosePerspectiveListener(uri));
removePageListener(this);
listeners.remove(uri);
}
}
}
}
}
/**
* A perspective listener which choices of an user
* @author tfaure
*
*/
protected class EditorClosePerspectiveListener implements IPerspectiveListener {
private final URI uri;
public EditorClosePerspectiveListener(URI uri) {
this.uri = uri;
}
public void perspectiveActivated(IWorkbenchPage page, IPerspectiveDescriptor perspective) {
}
public void perspectiveChanged(IWorkbenchPage page, IPerspectiveDescriptor perspective, String changeId) {
authorized.remove(uri);
mappingURIExtensions.remove(uri);
alreadyGuessed.remove(uri);
List<INotification> list = notifications.get(uri);
if(list != null) {
for(INotification n : list) {
n.delete();
}
}
notifications.remove(uri);
removePerspectiveListener(this);
}
}
/**
* A runnable refreshing resource and/or pages
*
* @author tfaure
*
*/
public class RefreshRunnable {
protected DiSashModelMngr sashModelMngr;
protected IPageMngr pageMngr;
protected URI uri;
protected final ModelSet modelSet;
protected final URI initialURI;
private final boolean refreshAll;
private final boolean refreshTab;
public RefreshRunnable(ModelSet modelSet, URI uriToLoad, URI initialURI, boolean refreshAll, boolean refreshTab) {
this.modelSet = modelSet;
uri = uriToLoad;
this.initialURI = initialURI;
this.refreshAll = refreshAll;
this.refreshTab = refreshTab;
}
public void run(CoreMultiDiagramEditor editor) {
final Set<URI> alreadyLoaded = new HashSet<URI>();
try {
sashModelMngr = editor.getServicesRegistry().getService(DiSashModelMngr.class);
pageMngr = sashModelMngr.getIPageMngr();
List<Object> allPages = sashModelMngr.getIPageMngr().allPages();
// the uri is added after getting all the pages. If it is done
// before, the eobjects are resolved
NotificationBuilder error = NotificationBuilder.createAsyncPopup(Messages.AskUserStrategy_ERROR, String.format(Messages.AskUserStrategy_UNABLE_TO_LOAD, uri != null ? uri.toString() : Messages.AskUserStrategy_12)).setType(Type.ERROR).setDelay(2000);
// load associated resources
// the extensions needed for each element are saved so it is not needed to check filesystem
Set<String> extensions = getExtensions();
if(extensions != null) {
for(String s : extensions) {
try {
if(s != null) {
URI uriToLoad = URI.createURI(uri.toString());
if(s != null) {
uriToLoad = uriToLoad.appendFileExtension(s);
}
// the resource is loaded only if it is needed
if(!alreadyLoaded.contains(uriToLoad)) {
Resource r = modelSet.getResource(uriToLoad, true);
alreadyLoaded.add(uriToLoad);
if(r == null) {
error.run();
}
}
}
} catch (Exception re) {
error.run();
re.printStackTrace();
}
}
}
// refresh tabs
for(Object o : allPages) {
if(o instanceof EObject) {
EObject eobject = (EObject)o;
URI eobjectURI = EcoreUtil.getURI(eobject);
if(refreshAll || (refreshTab && eobjectURI.trimFileExtension().trimFragment().equals(uri))) {
// TODO improve this when an update is created
pageMngr.closePage(o);
pageMngr.openPage(eobject);
}
}
}
} catch (ServiceException e1) {
e1.printStackTrace();
}
}
public Set<String> getExtensions() {
Map<URI, Set<String>> map = mappingURIExtensions.get(initialURI);
if(map != null) {
return map.get(uri);
}
return null;
}
public IWorkbenchPage getActivePage() {
IWorkbench workbench = PlatformUI.getWorkbench();
if(workbench != null) {
IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow();
if(activeWorkbenchWindow != null) {
if(activeWorkbenchWindow.getActivePage() != null) {
return activeWorkbenchWindow.getActivePage();
}
}
}
return null;
}
}
}