/*****************************************************************************
* 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
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.EList;
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.command.SetCommand;
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.jface.dialogs.MessageDialog;
import org.eclipse.papyrus.commands.wrappers.GMFtoEMFCommandWrapper;
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.utils.EditorUtils;
import org.eclipse.papyrus.infra.services.controlmode.commands.IUncontrolCommand.STATE_CONTROL;
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.historyPackage;
import org.eclipse.ui.PlatformUI;
/**
* The Class UncontrolCommand in charge of uncontrolling all papyrus resources
*
*/
public class UncontrolCommand 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 UNCONTROL_CMD_ATTRIBUTE_EXTENSION_POINT = "uncontrolCommand";
/** element ID for the custom command class. */
private static final String UNCONTROL_CMD_ELEMENT_EXTENSION_POINT = "customUncontrolCommand";
private EObject eObject;
private ModelSet modelSet;
private Resource controlledModel;
private Resource controlledNotation;
private Resource controlledDI;
private List<IUncontrolCommand> commands;
private List<ControledResource> controlledResourceToRemove;
private List<ControledResource> addedControlledResource;
private boolean deleteResources;
/**
* Instantiates a new uncontrol command.
*
* @param domain
* @param label
* @param affectedFiles
* @param selectedObject
*/
public UncontrolCommand(TransactionalEditingDomain domain, EObject selectedObject, String label, List<?> affectedFiles) {
this(domain, selectedObject, label, affectedFiles, false);
}
/**
* Instantiates a new uncontrol command.
*
* @param domain
* @param label
* @param affectedFiles
* @param selectedObject
* @param deleteUncontrolledResources whether to delete uncontrolled resources
*/
public UncontrolCommand(TransactionalEditingDomain domain, EObject selectedObject, String label, List<?> affectedFiles, boolean deleteUncontrolledResources) {
super(domain, label, affectedFiles);
this.eObject = selectedObject;
// Add an undo context to allow the editor to react to that change
addContext(new EditingDomainUndoContext(domain));
controlledResourceToRemove = new LinkedList<ControledResource>();
addedControlledResource = new LinkedList<ControledResource>();
deleteResources = deleteUncontrolledResources;
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 uncontrol command");
}
else {
result = CommandResult.newCancelledCommandResult();
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
protected IStatus doUndo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
// execute control command
ControlCommand transactionalCommand = new ControlCommand(modelSet.getTransactionalEditingDomain(), controlledModel, eObject, "Control", null);
modelSet.getTransactionalEditingDomain().getCommandStack().execute(new GMFtoEMFCommandWrapper(transactionalCommand));
return Status.OK_STATUS;
}
/**
* {@inheritDoc}
*/
@Override
protected IStatus doRedo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
if(eObject != null) {
controlledModel = eObject.eResource();
final URI newNotationURI = URI.createURI(controlledModel.getURI().trimFileExtension().appendFileExtension(NotationModel.NOTATION_FILE_EXTENSION).toString());
this.controlledNotation = getEditingDomain().getResourceSet().getResource(newNotationURI, true);
final URI newDiURI = URI.createURI(controlledModel.getURI().trimFileExtension().appendFileExtension(DiModel.DI_FILE_EXTENSION).toString());
this.controlledDI = getEditingDomain().getResourceSet().getResource(newDiURI, true);
}
if (modelSet == null) {
modelSet = EditorUtils.getDiResourceSet();
}
CompoundCommand compoundCommand = new CompoundCommand();
uncontrolNotation(compoundCommand);
uncontrolModel(compoundCommand);
// Ensure that all proxies are resolved so that references to the controlled object will be
// updated to reference the new resource.
EcoreUtil.resolveAll(getEditingDomain().getResourceSet());
if(compoundCommand.canExecute()) {
compoundCommand.execute();
// TODO save resources, check if it is useful
// try {
// diResourceSet.save(new NullProgressMonitor());
// } catch (IOException e) {
// EMFEditUIPlugin.INSTANCE.log(e);
// return Status.CANCEL_STATUS;
// }
deleteControlledResources();
return Status.OK_STATUS;
} else {
MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Unable to uncontrol", "Unable to execute uncontrol command");
return Status.CANCEL_STATUS;
}
}
/**
* Uncontrol the model resource
*
* @param compoundCommand
*/
private void uncontrolModel(CompoundCommand compoundCommand) {
// PRE uncontrol operation
Resource resource = UmlUtils.getUmlModel(modelSet).getResource();
uncontrol(getEditingDomain(), eObject, controlledModel, resource, compoundCommand, STATE_CONTROL.PRE_MODEL);
// Create the Command to Uncontrol the model object
compoundCommand.append(new RemoveCommand(getEditingDomain(), eObject.eResource().getContents(), eObject));
unassignControlledResourceOfCurrentElement(getEditingDomain(), compoundCommand, getDIResource(eObject), eObject.eResource().getURI().toString(), resource.getURI().toString());
// POST uncontrol operation
uncontrol(getEditingDomain(), eObject, controlledModel, resource, compoundCommand, STATE_CONTROL.POST_MODEL);
}
/**
* Uncontrol the notation resource
*
* @param compoundCommand
*/
private void uncontrolNotation(CompoundCommand compoundCommand) {
// First retrieve the Diagrams that match with the model object to Uncontrol
final List<Diagram> controlledDiagrams = NotationUtils.getDiagrams(controlledNotation, eObject);
if(!controlledDiagrams.isEmpty()) {
// PRE uncontrol operation
Resource notationResource = NotationUtils.getNotationModel(modelSet).getResource();
for(Diagram diag : controlledDiagrams) {
uncontrol(getEditingDomain(), diag, controlledNotation, notationResource, compoundCommand, STATE_CONTROL.PRE_NOTATION);
}
// uncontrol the Notation model
compoundCommand.append(new AddCommand(getEditingDomain(), notationResource.getContents(), controlledDiagrams));
Set<Resource> resources = new HashSet<Resource>(controlledDiagrams.size());
for(Diagram d : controlledDiagrams) {
resources.add(d.eResource());
}
for(Resource r : resources) {
unassignControlledResourceOfCurrentElement(getEditingDomain(), compoundCommand, getDIResource(eObject), r.getURI().toString(), notationResource.getURI().toString());
}
// POST uncontrol operation
for(Diagram diag : controlledDiagrams) {
uncontrol(getEditingDomain(), diag, controlledNotation, notationResource, compoundCommand, STATE_CONTROL.POST_NOTATION);
}
}
}
/**
* Analyze the history model to update the controlled children
*
* @param domain
* @param compoundCommand
* @param model
* @param currentURL
* @param newURL
*/
private void unassignControlledResourceOfCurrentElement(EditingDomain domain, CompoundCommand compoundCommand, Resource model, String oldURL, String newURL) {
controlledResourceToRemove.clear();
addedControlledResource.clear();
if(model != null) {
URI uriPath = HistoryUtils.getURIFullPath(newURL);
newURL = HistoryUtils.resolve(uriPath, newURL);
oldURL = HistoryUtils.resolve(uriPath, oldURL);
Set<ControledResource> controledOldURL = new HashSet<ControledResource>(HistoryUtils.getControledResourcesForURL(modelSet, oldURL));
controledOldURL.addAll(HistoryUtils.getControledResourcesForURL(modelSet, oldURL.substring(oldURL.lastIndexOf("/")+1,oldURL.length())));
List<ControledResource> controledNewURL = HistoryUtils.getControledResourcesForURL(modelSet, newURL);
for(ControledResource resourceOldURL : controledOldURL) {
if(resourceOldURL.getChildren().isEmpty()) {
// store the controlled resource to remove
controlledResourceToRemove.add(resourceOldURL);
} else {
if(resourceOldURL.eContainer() instanceof ControledResource) {
compoundCommand.append(AddCommand.create(domain, resourceOldURL.eContainer(), historyPackage.Literals.CONTROLED_RESOURCE__CHILDREN, Collections.singleton(resourceOldURL)));
} else {
for(ControledResource resourceNewURL : controledNewURL) {
// add children of the old controlled resource to the controlled resource with the new URL
compoundCommand.append(AddCommand.create(domain, resourceNewURL, historyPackage.Literals.CONTROLED_RESOURCE__CHILDREN, resourceOldURL.getChildren()));
addedControlledResource.addAll(resourceOldURL.getChildren());
// resolve url to be relative to the new resource
for(ControledResource c : resourceOldURL.getChildren()) {
String childRelativeUrl = c.getResourceURL();
URI absoluteChildPath = URI.createURI(c.eResource().getURI().trimSegments(1).toString() + "/");
URI absoluteChildURI = URI.createURI(childRelativeUrl).resolve(absoluteChildPath);
String urlResolved = absoluteChildURI.deresolve(uriPath).toString();
compoundCommand.append(SetCommand.create(domain, c, historyPackage.Literals.CONTROLED_RESOURCE__RESOURCE_URL, urlResolved));
}
}
}
}
}
// remove children and parent if needed
for(ControledResource parent : controledNewURL) {
if(controlledResourceToRemove.containsAll(parent.getChildren()) && addedControlledResource.isEmpty()) {
compoundCommand.append(new RemoveCommand(domain, parent.eResource().getContents(), parent));
} else {
for(ControledResource r : controlledResourceToRemove) {
compoundCommand.append(RemoveCommand.create(domain, r.eContainer(), historyPackage.Literals.CONTROLED_RESOURCE__CHILDREN, r));
}
}
}
}
}
/**
* Get the history resource of the specified eObject
*
* @param eObject
* @return
*/
private Resource getDIResource(EObject eObject) {
// uncontrol command is only available from its parent. With this condition, the current sashModel is the parent
return SashModelUtils.getSashModel(modelSet).getResource();
}
/**
* Control action applied on the specified selection
*
* @param domain
* @param selection
* @param source
* @param target
* @param command
* @param state
*/
public void uncontrol(EditingDomain domain, EObject selection, Resource source, Resource target, CompoundCommand command, STATE_CONTROL state) {
for(IUncontrolCommand cmd : commands) {
if(cmd.provides(selection, state, source, target)) {
cmd.uncontrol(domain, selection, state, source, target, command);
}
}
}
/**
* Gets the custom command extensions that will be executed with the default uncontrol action.
*
* @return the command extensions
*/
private List<IUncontrolCommand> getCommandExtensions() {
List<IUncontrolCommand> commands = new LinkedList<IUncontrolCommand>();
IConfigurationElement[] extensions = Platform.getExtensionRegistry().getConfigurationElementsFor(CONTROL_EXTENSION_POINT_ID);
for(IConfigurationElement e : extensions) {
if(UNCONTROL_CMD_ELEMENT_EXTENSION_POINT.equals(e.getName())) {
try {
IUncontrolCommand uncontrolCmd = (IUncontrolCommand)e.createExecutableExtension(UNCONTROL_CMD_ATTRIBUTE_EXTENSION_POINT);
commands.add(uncontrolCmd);
} catch (CoreException exception) {
exception.printStackTrace();
}
}
}
return commands;
}
/**
* Delete the controlled resources.
*/
private void deleteControlledResources() {
// Remove the controlled resources from the resource set.
EList<Resource> resources = getEditingDomain().getResourceSet().getResources();
resources.remove(controlledModel);
resources.remove(controlledNotation);
resources.remove(controlledDI);
Collection<IResource> todelete = new ArrayList<IResource>();
addFileResource(controlledModel, todelete);
addFileResource(controlledNotation, todelete);
addFileResource(controlledDI, todelete);
// if confirmed delete is false, uncontrol is done and old controlled resource is a single
// resource
if(deleteResources) {
for(IResource file : todelete) {
try {
file.delete(true, new NullProgressMonitor());
} catch (CoreException exception) {
EMFEditUIPlugin.INSTANCE.log(exception);
}
}
}
}
/**
* private method that comes from org.topcased.modeler.internal.actions.ModelerUncontrolAction
*/
private void addFileResource(Resource emfRes, Collection<IResource> fileResources) {
URI uri = (emfRes != null) ? emfRes.getURI() : null;
if(uri != null && uri.isPlatformResource()) {
IPath path = new Path(uri.toPlatformString(false));
IResource r = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
if(r != null) {
fileResources.add(r);
}
}
}
}