/*****************************************************************************
* Copyright (c) 2009 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:
* Emilien Perico (Atos Origin) emilien.perico@atosorigin.com - Initial API and implementation
*
*****************************************************************************/
package org.eclipse.papyrus.infra.services.controlmode.commands;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.command.CompoundCommand;
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.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.ui.EMFEditUIPlugin;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.gmf.runtime.common.core.command.CommandResult;
import org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand;
import org.eclipse.gmf.runtime.emf.commands.core.command.EditingDomainUndoContext;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.papyrus.commands.wrappers.GMFtoEMFCommandWrapper;
import org.eclipse.papyrus.infra.core.modelsetquery.ModelSetQuery;
import org.eclipse.papyrus.infra.core.resource.ModelSet;
import org.eclipse.papyrus.infra.core.resource.notation.NotationModel;
import org.eclipse.papyrus.infra.core.resource.notation.NotationUtils;
import org.eclipse.papyrus.infra.core.resource.sasheditor.DiModel;
import org.eclipse.papyrus.infra.core.resource.sasheditor.SashModelUtils;
import org.eclipse.papyrus.infra.core.resource.uml.UmlUtils;
import org.eclipse.papyrus.infra.core.sashwindows.di.PageRef;
import org.eclipse.papyrus.infra.core.sashwindows.di.SashWindowsMngr;
import org.eclipse.papyrus.infra.core.sashwindows.di.exception.SashEditorException;
import org.eclipse.papyrus.infra.core.sashwindows.di.util.DiUtils;
import org.eclipse.papyrus.infra.core.utils.EditorUtils;
import org.eclipse.papyrus.infra.services.controlmode.commands.IControlCommand.STATE_CONTROL;
import org.eclipse.papyrus.infra.services.controlmode.history.HistoryModel;
import org.eclipse.papyrus.infra.services.controlmode.history.utils.HistoryUtils;
import org.eclipse.papyrus.infra.services.controlmode.mm.history.ControledResource;
import org.eclipse.papyrus.infra.services.controlmode.mm.history.historyFactory;
import org.eclipse.papyrus.infra.services.controlmode.mm.history.historyPackage;
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.widgets.Display;
import org.eclipse.ui.PlatformUI;
/**
* The Class ControlCommand in charge of controlling all papyrus resources
*/
public class ControlCommand extends AbstractTransactionalCommand {
/** extension point ID for custom control command */
private static final String CONTROL_EXTENSION_POINT_ID = "org.eclipse.papyrus.infra.services.controlmode.customControlCommand";
/** attribute ID for the custom command class. */
private static final String CONTROL_CMD_ATTRIBUTE_EXTENSION_POINT = "controlCommand";
/** element ID for the custom command class. */
private static final String CONTROL_CMD_ELEMENT_EXTENSION_POINT = "customControlCommand";
protected EObject eObject;
protected ModelSet modelSet;
protected Resource controlledModel;
protected Resource controlledNotation;
protected Resource controlledDI;
protected List<IControlCommand> commands;
/**
* Instantiates a new control command.
*
* @param domain
* @param label
* @param affectedFiles
*/
public ControlCommand(TransactionalEditingDomain domain, Resource model, EObject selectedObject, String label, List<?> affectedFiles) {
super(domain, label, affectedFiles);
this.eObject = selectedObject;
this.controlledModel = model;
// Add an undo context to allow the editor to react to that change
addContext(new EditingDomainUndoContext(domain));
ResourceSet set = domain.getResourceSet();
if (set instanceof ModelSet) {
modelSet = (ModelSet) set;
}
}
/**
* {@inheritDoc}
*/
@Override
protected CommandResult doExecuteWithResult(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
commands = getCommandExtensions();
IStatus status = doRedo(monitor, info);
CommandResult result;
if (status.equals(Status.OK_STATUS)) {
result = CommandResult.newOKCommandResult();
}
else if (status.equals(Status.CANCEL_STATUS)) {
result = CommandResult.newErrorCommandResult("Unable to execute control command");
}
else {
result = CommandResult.newCancelledCommandResult();
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
protected IStatus doUndo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
// execute uncontrol command
UncontrolCommand transactionalCommand = new UncontrolCommand(getEditingDomain(), eObject, "Uncontrol", null, true);
getEditingDomain().getCommandStack().execute(new GMFtoEMFCommandWrapper(transactionalCommand));
return Status.OK_STATUS;
}
/**
* {@inheritDoc}
*/
@Override
protected IStatus doRedo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
if (modelSet == null) {
modelSet = EditorUtils.getDiResourceSet();
}
// Create the URI from models that will be created
final URI newNotationURI = URI.createURI(controlledModel.getURI().trimFileExtension().appendFileExtension(NotationModel.NOTATION_FILE_EXTENSION).toString());
this.controlledNotation = getResource(newNotationURI);
final URI newDiURI = URI.createURI(controlledModel.getURI().trimFileExtension().appendFileExtension(DiModel.DI_FILE_EXTENSION).toString());
this.controlledDI = getResource(newDiURI);
final List<Diagram> diagrams = getDiagrams(eObject);
// compound command that executes whole control action
CompoundCommand compoundCommand = new CompoundCommand();
controlModel(compoundCommand);
controlNotation(compoundCommand, diagrams);
try {
controlDi(compoundCommand, diagrams);
} catch (SashEditorException exception) {
EMFEditUIPlugin.INSTANCE.log(exception);
return Status.CANCEL_STATUS;
}
// Ensure that all proxies are resolved so that references into the controlled object will be saved to reference the new resource
EcoreUtil.resolveAll(getEditingDomain().getResourceSet());
if(compoundCommand.canExecute()) {
compoundCommand.execute();
return Status.OK_STATUS;
} else {
return Status.CANCEL_STATUS;
}
}
/**
* Search for diagrams in corresponding notation resource of the eObject model resource
*
* @param eObject
* @return
*/
protected List<Diagram> getDiagrams(EObject eObject) {
List<Diagram> diagrams = new LinkedList<Diagram>();
// search for diagrams only in the notation resource corresponding to the model resource where eObject is.
// Some "sub diagrams" in the model can be in controlled resource, we must not move them.
Resource resource = eObject.eResource();
Resource notationResource = modelSet.getResource(resource.getURI().trimFileExtension().appendFileExtension(NotationModel.NOTATION_FILE_EXTENSION), false);
diagrams.addAll(NotationUtils.getDiagrams(notationResource, eObject));
return diagrams;
}
// /**
// * Search for diagrams in all the resources
// *
// * @param eObject
// * @return
// */
// private List<Diagram> getDiagrams(EObject eObject) {
// List<Diagram> diagrams = new LinkedList<Diagram>();
// for(Resource r : diResourceSet.getResources()) {
// if(NotationModel.NOTATION_FILE_EXTENSION.equals(r.getURI().fileExtension())) {
// diagrams.addAll(NotationUtils.getDiagrams(r, eObject));
// }
// }
// return diagrams;
// }
/**
* Control the model resource
*
* @param compoundCommand
*/
protected void controlModel(CompoundCommand compoundCommand) {
// PRE control operation
control(getEditingDomain(), eObject, UmlUtils.getUmlModel(modelSet).getResource(), controlledModel, compoundCommand, STATE_CONTROL.PRE_MODEL);
// Control the Model
compoundCommand.append(new AddCommand(getEditingDomain(), controlledModel.getContents(), eObject));
// update history
assignControlledResourceOfCurrentElement(getEditingDomain(), compoundCommand, getHistoryResource(eObject), eObject.eResource().getURI().toString(), controlledModel.getURI().toString());
// POST control operation
control(getEditingDomain(), eObject, UmlUtils.getUmlModel(modelSet).getResource(), controlledModel, compoundCommand, STATE_CONTROL.POST_MODEL);
}
/**
* Control the notation resource
*
* @param compoundCommand
* @param diagrams
* list
*/
protected void controlNotation(CompoundCommand compoundCommand, List<Diagram> diagrams) {
// PRE control operation
for(Diagram diag : diagrams) {
control(getEditingDomain(), diag, getNotationResourceForCurrent(eObject), controlledNotation, compoundCommand, STATE_CONTROL.PRE_NOTATION);
}
if(!diagrams.isEmpty()) {
// Control the Notation model
compoundCommand.append(new AddCommand(getEditingDomain(), controlledNotation.getContents(), diagrams));
}
// update history
Set<Resource> resources = new HashSet<Resource>(diagrams.size());
for(Diagram d : diagrams) {
resources.add(d.eResource());
}
for(Resource r : resources) {
assignControlledResourceOfCurrentElement(getEditingDomain(), compoundCommand, getHistoryResource(eObject), r.getURI().toString(), controlledNotation.getURI().toString());
}
// POST control operation
for(Diagram diag : diagrams) {
control(getEditingDomain(), diag, getNotationResourceForCurrent(eObject), controlledNotation, compoundCommand, STATE_CONTROL.POST_NOTATION);
}
}
/**
* Control the di resource
*
* @param compoundCommand
* @param diagrams
* @throws SashEditorException
*/
protected void controlDi(CompoundCommand compoundCommand, final List<Diagram> diagrams) throws SashEditorException {
// Create a new SashWindowManager
SashWindowsMngr windowsMngr = DiUtils.createDefaultSashWindowsMngr();
Resource diResource = SashModelUtils.getSashModel(modelSet).getResource();
// add pages to the page list
for(Diagram diagram : diagrams) {
PageRef pageRef = DiUtils.getPageRef(diResource, diagram);
if(pageRef != null) {
windowsMngr.getPageList().addPage(pageRef.getPageIdentifier());
DiUtils.addPageToTabFolder(windowsMngr, pageRef);
}
}
// PRE control operation
control(getEditingDomain(), eObject, diResource, controlledDI, compoundCommand, STATE_CONTROL.PRE_DI);
// Control the DI model
compoundCommand.append(new AddCommand(getEditingDomain(), controlledDI.getContents(), windowsMngr, 0));
// POST control operation
control(getEditingDomain(), eObject, diResource, controlledDI, compoundCommand, STATE_CONTROL.POST_DI);
}
/**
* Display asynchronous popup to inform user about the control action
*/
protected void notifySave() {
new NotificationBuilder().setMessage("Your element has been controlled.\nYou need to save your model to see modifications in your workspace.\nDo you want to save ?").addAction(new NotificationRunnable() {
public void run(IContext context) {
try {
Display.getDefault().syncExec(new Runnable() {
public void run() {
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor().doSave(new NullProgressMonitor());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
public String getLabel() {
return "Save";
}
}).setTemporary(true).setAsynchronous(true).setType(Type.INFO).setDelay(2000).run();
}
/**
* Analyze the history model to update the controlled children
*
* @param domain
* @param compoundCommand
* @param model
* @param currentURL
* @param newURL
*/
protected void assignControlledResourceOfCurrentElement(EditingDomain domain, CompoundCommand compoundCommand, Resource model, String currentURL, String newURL) {
if(model == null) {
return;
}
// create relative path
URI uriPath = HistoryUtils.getURIFullPath(currentURL);
String currentURLResolved = HistoryUtils.resolve(uriPath, currentURL);
String newURLResolved = HistoryUtils.resolve(uriPath, newURL);
ControledResource child = historyFactory.eINSTANCE.createControledResource();
child.setResourceURL(newURLResolved);
ControledResource resource = getControledResource(model);
Resource parentResource = null;
// create the controlled resource according to the control action
ControledResource parent = null;
if(resource == null) {
parent = historyFactory.eINSTANCE.createControledResource();
parent.setResourceURL(currentURLResolved);
parent.getChildren().add(child);
parentResource = model;
compoundCommand.append(new AddCommand(domain, parentResource.getContents(), Collections.singleton(parent)));
} else {
if(isCurrentURL(currentURLResolved, resource)) {
parent = resource;
}
if(parent == null) {
EObject modelRoot = getControledResource(model);
Collection<EObject> controled = ModelSetQuery.getObjectsOfType(modelRoot, historyPackage.Literals.CONTROLED_RESOURCE);
for(EObject next : controled) {
if(next instanceof ControledResource) {
ControledResource tmp = (ControledResource)next;
if(isCurrentURL(currentURLResolved, tmp)) {
parent = tmp;
break;
}
}
}
}
if(parent == null) {
parent = historyFactory.eINSTANCE.createControledResource();
parent.setResourceURL(currentURLResolved);
parentResource = resource.eResource();
// add controlled resource command for notation is done in assignToChildExistingControledResources
if(!currentURLResolved.endsWith(NotationModel.NOTATION_FILE_EXTENSION)) {
compoundCommand.append(new AddCommand(domain, parentResource.getContents(), Collections.singleton(parent)));
}
}
if(parent != null) {
compoundCommand.append(AddCommand.create(domain, parent, historyPackage.Literals.CONTROLED_RESOURCE__CHILDREN, Collections.singleton(child)));
}
}
List<ControledResource> controledFromParent = new LinkedList<ControledResource>();
if(parentResource == null) {
parentResource = model;
}
if(parentResource != null) {
for(EObject e : parentResource.getContents()) {
if(e instanceof ControledResource) {
ControledResource aControled = (ControledResource)e;
controledFromParent.add(aControled);
for(Iterator<EObject> i = aControled.eAllContents(); i.hasNext();) {
EObject tmp = i.next();
if(tmp instanceof ControledResource) {
controledFromParent.add((ControledResource)tmp);
}
}
}
}
}
// manage move of existing controlled resources
if(!newURL.endsWith(NotationModel.NOTATION_FILE_EXTENSION)) {
assignToChildExistingControledResources(domain, compoundCommand, child, newURL, controledFromParent, currentURL, URI.createURI(newURL), uriPath);
}
}
/**
* Manage move of existing controlled resources
*
* @param domain
* @param compoundCommand
* @param child
* @param controledResourceURL
* @param controledFromParent
* @param parentURL
* @param controledURIFullPath
* @param parentURIFullPath
*/
protected void assignToChildExistingControledResources(EditingDomain domain, CompoundCommand compoundCommand, ControledResource child, String controledResourceURL, List<ControledResource> controledFromParent, String parentURL, URI controledURIFullPath, URI parentURIFullPath) {
for(ControledResource r : controledFromParent) {
if(r.getResourceURL() != null) {
URI fullPathParent = URI.createURI(r.getResourceURL()).resolve(parentURIFullPath);
Resource resourceLoaded = modelSet.getResource(fullPathParent, false);
if(resourceLoaded != null && !resourceLoaded.getContents().isEmpty()) {
EObject top = resourceLoaded.getContents().get(0);
if(isInRootHierarchy(top, eObject)) {
// manage model
URI newResolvedURIFromChild = fullPathParent.deresolve(controledURIFullPath, false, true, true);
ControledResource aNewOne = historyFactory.eINSTANCE.createControledResource();
aNewOne.setResourceURL(newResolvedURIFromChild.toString());
// add new control resource to the new history
compoundCommand.append(new AddCommand(domain, getControledResource(controlledDI, URI.createURI(controledResourceURL).lastSegment(), compoundCommand, getEditingDomain()), historyPackage.Literals.CONTROLED_RESOURCE__CHILDREN, aNewOne));
// remove old controlled resource from the parent resource
compoundCommand.append(RemoveCommand.create(domain, r.eContainer(), historyPackage.Literals.CONTROLED_RESOURCE__CHILDREN, r));
// manage notation
URI newNotation = newResolvedURIFromChild.trimFileExtension().appendFileExtension(NotationModel.NOTATION_FILE_EXTENSION);
ControledResource aNewOneNotation = historyFactory.eINSTANCE.createControledResource();
aNewOneNotation.setResourceURL(newNotation.toString());
// add new control resource to the new history
compoundCommand.append(new AddCommand(domain, getControledResource(controlledDI, URI.createURI(URI.createURI(controledResourceURL).trimFileExtension().appendFileExtension(NotationModel.NOTATION_FILE_EXTENSION).toString()).lastSegment(), compoundCommand, getEditingDomain()), historyPackage.Literals.CONTROLED_RESOURCE__CHILDREN, aNewOneNotation));
// remove old controlled resource from the parent resource
URI notationParentURL = URI.createURI(r.getParent().getResourceURL()).trimFileExtension().appendFileExtension(NotationModel.NOTATION_FILE_EXTENSION);
ControledResource notationParent = getControledResource(r.eResource(), notationParentURL.toString(), compoundCommand, domain);
for(ControledResource notationChild : notationParent.getChildren()) {
//URI notationURI = newResolvedURIFromChild.trimFileExtension().appendFileExtension(NotationModel.NOTATION_FILE_EXTENSION);
URI notationURI = URI.createURI(r.getResourceURL()).trimFileExtension().appendFileExtension(NotationModel.NOTATION_FILE_EXTENSION);
if(notationChild.getResourceURL().equals(notationURI.toString())) {
compoundCommand.append(RemoveCommand.create(domain, notationParent, historyPackage.Literals.CONTROLED_RESOURCE__CHILDREN, notationChild));
}
}
}
}
}
}
}
/**
* Get the controlled resource in a specified resource
*/
protected ControledResource getControledResource(Resource resource) {
for(EObject e : resource.getContents()) {
if(e instanceof ControledResource) {
return (ControledResource)e;
}
}
return null;
}
/**
* Get the controlled resource for the specified URL, create it if it doesn't exist
*
* @param resource
* @param controledResourceURL
* @param command
* @param domain
* @return
*/
protected ControledResource getControledResource(Resource resource, String controledResourceURL, CompoundCommand command, EditingDomain domain) {
ControledResource result = null;
for(EObject e : resource.getContents()) {
if(e instanceof ControledResource) {
ControledResource controled = (ControledResource)e;
if(controledResourceURL != null && controledResourceURL.equals(controled.getResourceURL())) {
result = controled;
}
}
}
if(result == null) {
result = historyFactory.eINSTANCE.createControledResource();
result.setResourceURL(controledResourceURL);
command.append(new AddCommand(domain, resource.getContents(), result, 0));
}
return result;
}
/**
* Check if an EObject start is in root hierarchy of another eObject search
*
* @param start
* @param search
* @return
*/
protected boolean isInRootHierarchy(EObject start, EObject search) {
while(start != null && start != search) {
start = start.eContainer();
}
return start != null;
}
/**
* Check if an URL is the current one of the specified resource
*
* @param currentURL
* @param resource
* @return
*/
protected boolean isCurrentURL(String currentURL, ControledResource resource) {
return resource.getResourceURL() != null && resource.getResourceURL().equals(currentURL);
}
/**
* Get the notation resource for the specified eObject
*
* @param eObject
* @return
*/
protected Resource getNotationResourceForCurrent(EObject eObject) {
return modelSet.getResource(eObject.eResource().getURI().trimFileExtension().appendFileExtension(NotationModel.NOTATION_FILE_EXTENSION), true);
}
/**
* Get the history resource of the specified eObject
*
* @param eObject
* @return
*/
protected Resource getHistoryResource(EObject eObject) {
if (eObject.eResource() != null) {
return modelSet.getResource(eObject.eResource().getURI().trimFileExtension().appendFileExtension(HistoryModel.MODEL_FILE_EXTENSION), true);
}
return null;
}
/**
* Control action applied on the specified selection
*
* @param domain
* @param selection
* @param source
* @param target
* @param command
* @param state
*/
public void control(EditingDomain domain, EObject selection, Resource source, Resource target, CompoundCommand command, STATE_CONTROL state) {
for(IControlCommand cmd : commands) {
if(cmd.provides(selection, state, source, target)) {
cmd.control(domain, selection, state, source, target, command);
}
}
}
/**
* Gets the custom command extensions that will be executed with the default
* control action.
*
* @return the command extensions
*/
private List<IControlCommand> getCommandExtensions() {
List<IControlCommand> commands = new LinkedList<IControlCommand>();
IConfigurationElement[] extensions = Platform.getExtensionRegistry().getConfigurationElementsFor(CONTROL_EXTENSION_POINT_ID);
for(IConfigurationElement e : extensions) {
if(CONTROL_CMD_ELEMENT_EXTENSION_POINT.equals(e.getName())) {
try {
IControlCommand controlCmd = (IControlCommand)e.createExecutableExtension(CONTROL_CMD_ATTRIBUTE_EXTENSION_POINT);
commands.add(controlCmd);
} catch (CoreException exception) {
exception.printStackTrace();
}
}
}
return commands;
}
/**
* Gets the resource from the URI, create it if not exists
*
* @param uri
*
* @return the resource
*/
protected Resource getResource(URI uri) {
Resource res = getEditingDomain().getResourceSet().getResource(uri, false);
if(res == null) {
res = getEditingDomain().getResourceSet().createResource(uri);
}
return res;
}
}