package org.eclipse.xtext.gmf.glue.concurrency; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.common.notify.impl.AdapterFactoryImpl; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.transaction.NotificationFilter; import org.eclipse.emf.transaction.ResourceSetChangeEvent; import org.eclipse.emf.transaction.ResourceSetListener; import org.eclipse.emf.transaction.RollbackException; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.emf.transaction.TransactionalEditingDomain.Lifecycle; import org.eclipse.emf.transaction.TransactionalEditingDomainEvent; import org.eclipse.emf.transaction.TransactionalEditingDomainListener; import org.eclipse.emf.transaction.util.TransactionUtil; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.xtext.gmf.glue.Activator; import org.eclipse.xtext.resource.IResourceServiceProvider; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.ui.editor.IDirtyResource; import org.eclipse.xtext.ui.editor.IDirtyStateManager; import org.eclipse.xtext.ui.shared.Access; import com.google.common.collect.Lists; import com.google.common.collect.Maps; /** * This element comes from the XText/GMF integration example, and was not originally documented. */ public class EditingDomainAdapter extends AdapterImpl implements ResourceSetListener, TransactionalEditingDomainListener { /** * This element comes from the XText/GMF integration example, and was not originally documented. */ public static class Factory extends AdapterFactoryImpl { @Override public boolean isFactoryForType(Object type) { return super.isFactoryForType(type == EditingDomainAdapter.class); } @Override protected Object resolve(Object object, Object type) { if (object instanceof TransactionalEditingDomain) { return adapt(((TransactionalEditingDomain) object).getResourceSet(), type); } return super.resolve(object, type); } @Override protected Adapter createAdapter(Notifier target) { if (target instanceof ResourceSet) { return new EditingDomainAdapter(TransactionUtil.getEditingDomain(target)); } else { return null; } } } private final TransactionalEditingDomain editingDomain; private IDirtyStateManager dirtyStateManager; private Map<URI, IDirtyResource> uri2dirtyResource; private static final Logger LOG = Logger.getLogger(EditingDomainAdapter.class); protected EditingDomainAdapter(TransactionalEditingDomain editingDomain) { this.editingDomain = editingDomain; editingDomain.addResourceSetListener(this); dirtyStateManager = Access.getIDirtyStateManager().get(); uri2dirtyResource = Maps.newHashMap(); Lifecycle lifecycle = TransactionUtil.getAdapter(editingDomain, Lifecycle.class); lifecycle.addTransactionalEditingDomainListener(this); } public NotificationFilter getFilter() { return null; } public boolean isAggregatePrecommitListener() { return true; } public boolean isPostcommitOnly() { return false; } public boolean isPrecommitOnly() { return false; } public void resourceSetChanged(ResourceSetChangeEvent event) { List<URI> remainingURIs = Lists.newArrayList(uri2dirtyResource.keySet()); for (Resource resource : editingDomain.getResourceSet().getResources()) { if (resource instanceof XtextResource) { XtextResource xtextResource = (XtextResource) resource; remainingURIs.remove(xtextResource.getURI()); IDirtyResource dirtyResource = uri2dirtyResource.get(xtextResource.getURI()); if (xtextResource.isModified()) { if (dirtyResource == null) { createAndRegisterDirtyState(xtextResource); } } else { if (dirtyResource != null) { uri2dirtyResource.remove(xtextResource.getURI()); dirtyStateManager.discardDirtyState(dirtyResource); } } } } // removed resources for (URI remainingURI : remainingURIs) { IDirtyResource dirtyResource = uri2dirtyResource.get(remainingURI); dirtyStateManager.discardDirtyState(dirtyResource); uri2dirtyResource.remove(remainingURI); } } public Command transactionAboutToCommit(ResourceSetChangeEvent event) throws RollbackException { List<XtextResource> resourcesWithConflicts = null; for (Resource resource : editingDomain.getResourceSet().getResources()) { if (resource instanceof XtextResource && resource.isModified()) { XtextResource xtextResource = (XtextResource) resource; IDirtyResource dirtyResource = uri2dirtyResource.get(xtextResource.getURI()); if (dirtyResource == null) { if (!createAndRegisterDirtyState(xtextResource)) { if (resourcesWithConflicts == null) { resourcesWithConflicts = Lists.newArrayList(); } resourcesWithConflicts.add(xtextResource); } } } } if (resourcesWithConflicts != null) { if (queryApplyChanges()) { for (XtextResource resourceWithConflicts : resourcesWithConflicts) { try { IDirtyResource dirtyResource = createDirtyResource(resourceWithConflicts); // assert resource is serializable dirtyResource.getContents(); dirtyStateManager.announceDirtyStateChanged(dirtyResource); } catch (Exception exc) { LOG.error("Error serializing resource", exc); } } } else { throw new RollbackException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Transaction aborted by user")); } } return null; } protected boolean createAndRegisterDirtyState(XtextResource xtextResource) { IDirtyResource dirtyResource = createDirtyResource(xtextResource); if (dirtyResource == null) { return true; } else { boolean isSuccess = dirtyStateManager.manageDirtyState(dirtyResource); if (isSuccess) { uri2dirtyResource.put(xtextResource.getURI(), dirtyResource); } return isSuccess; } } protected IDirtyResource createDirtyResource(XtextResource xtextResource) { IResourceServiceProvider resourceServiceProvider = IResourceServiceProvider.Registry.INSTANCE .getResourceServiceProvider(xtextResource.getURI()); if (resourceServiceProvider == null) { return null; } return new SimpleDirtyResource(xtextResource, resourceServiceProvider); } public void editingDomainDisposing(TransactionalEditingDomainEvent event) { dispose(); } /** * This element comes from the XText/GMF integration example, and was not originally documented. */ public void dispose() { if (uri2dirtyResource != null) { for (IDirtyResource dirtyResource : uri2dirtyResource.values()) { dirtyStateManager.discardDirtyState(dirtyResource); } uri2dirtyResource = null; } Lifecycle lifecycle = TransactionUtil.getAdapter(editingDomain, Lifecycle.class); if (lifecycle != null) { lifecycle.removeTransactionalEditingDomainListener(this); } editingDomain.removeResourceSetListener(this); } protected boolean queryApplyChanges() { DialogPrompter dialogPrompter = new DialogPrompter(); Display.getDefault().syncExec(dialogPrompter); boolean yesResult = dialogPrompter.isYesResult(); return yesResult; } protected class DialogPrompter implements Runnable { private boolean isYesResult; public boolean isYesResult() { return isYesResult; } public void run() { Shell shell = Display.getDefault().getActiveShell(); isYesResult = MessageDialog.open(MessageDialog.QUESTION, shell, "Concurrent Modification", "Other editors have a modified version of models you are going to change.\n" + "If apply your changes you are loosing the possibility to save the others.\n" + "Apply changes anyway?", SWT.NONE); } } public void transactionClosed(TransactionalEditingDomainEvent event) { // do nothing } public void transactionClosing(TransactionalEditingDomainEvent event) { // do nothing } public void transactionInterrupted(TransactionalEditingDomainEvent event) { // do nothing } public void transactionStarted(TransactionalEditingDomainEvent event) { // do nothing } public void transactionStarting(TransactionalEditingDomainEvent event) { // do nothing } }