blob: 3e47d8238f639c60cca525f78bef41a48cb43dd3 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2011-2014 CEA LIST and others.
*
* 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:
*
* CEA LIST - Initial API and implementation
* Christian W. Damus (CEA) - bug 402525
*
*****************************************************************************/
package org.eclipse.papyrus.sysml.service.types.helper.advice;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.gmf.runtime.common.core.command.AbstractCommand;
import org.eclipse.gmf.runtime.common.core.command.CommandResult;
import org.eclipse.gmf.runtime.common.core.command.CompositeCommand;
import org.eclipse.gmf.runtime.common.core.command.ICommand;
import org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand;
import org.eclipse.gmf.runtime.emf.core.util.EMFCoreUtil;
import org.eclipse.gmf.runtime.emf.type.core.ISpecializationType;
import org.eclipse.gmf.runtime.emf.type.core.edithelper.AbstractEditHelperAdvice;
import org.eclipse.gmf.runtime.emf.type.core.requests.DestroyDependentsRequest;
import org.eclipse.gmf.runtime.emf.type.core.requests.DestroyElementRequest;
import org.eclipse.gmf.runtime.emf.type.core.requests.ReorientRelationshipRequest;
import org.eclipse.gmf.runtime.emf.type.core.requests.ReorientRequest;
import org.eclipse.gmf.runtime.emf.type.core.requests.SetRequest;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.papyrus.infra.services.edit.service.ElementEditServiceUtils;
import org.eclipse.papyrus.infra.services.edit.service.IElementEditService;
import org.eclipse.papyrus.infra.services.edit.utils.RequestParameterConstants;
import org.eclipse.papyrus.sysml.constraints.ConstraintBlock;
import org.eclipse.papyrus.sysml.constraints.ConstraintProperty;
import org.eclipse.papyrus.sysml.constraints.ConstraintsPackage;
import org.eclipse.papyrus.sysml.service.types.Activator;
import org.eclipse.papyrus.sysml.service.types.element.SysMLElementTypes;
import org.eclipse.papyrus.sysml.service.types.utils.ConnectorUtils;
import org.eclipse.papyrus.uml.diagram.common.util.CrossReferencerUtil;
import org.eclipse.papyrus.uml.service.types.utils.ElementUtil;
import org.eclipse.papyrus.uml.service.types.utils.NamedElementHelper;
import org.eclipse.swt.widgets.Display;
import org.eclipse.uml2.common.util.CacheAdapter;
import org.eclipse.uml2.uml.Association;
import org.eclipse.uml2.uml.Connector;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Port;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.Type;
import org.eclipse.uml2.uml.UMLFactory;
import org.eclipse.uml2.uml.UMLPackage;
import org.eclipse.uml2.uml.util.UMLUtil;
import org.eclipse.uml2.uml.util.UMLUtil.StereotypeApplicationHelper;
/**
* <pre>
* This HelperAdvice completes {@link Property} edit commands with:
* - possibly required (sysML) association re-factor command.
* </pre>
*/
public class PropertyEditHelperAdvice extends AbstractEditHelperAdvice {
/**
* <pre>
* {@inheritDoc}
*
* While setting {@link Property} (excluding {@link Port} and {@link ConstraintParameter} type:
* - add possibly required (sysML) association re-factor command when needed.
* - add/remove possibly required ConstraintProperty stereotype when needed.
* </pre>
*/
@SuppressWarnings("unchecked")
@Override
protected ICommand getBeforeSetCommand(SetRequest request) {
ICommand gmfCommand = super.getBeforeSetCommand(request);
EObject elementToEdit = request.getElementToEdit();
if ((elementToEdit instanceof Property) && !(elementToEdit instanceof Port) && (request.getFeature() == UMLPackage.eINSTANCE.getTypedElement_Type()) && (request.getValue() instanceof Type)) {
Property propertyToEdit = (Property) elementToEdit;
// SysML specification : all property typed by a ConstraintBlock must have a ContraintProperty stereotype applied
if (request.getValue() instanceof org.eclipse.uml2.uml.Class) {
ICommand stereotypeApplicationCommand = getConstraintPropertyStereotypeApplicationCommand(propertyToEdit, (org.eclipse.uml2.uml.Class) request.getValue(), request);
gmfCommand = CompositeCommand.compose(gmfCommand, stereotypeApplicationCommand);
}
// Exclude ConstraintParameter (simple property without ConstraintProperty stereotype owned by a ConstraintBlock)
if (propertyToEdit.eContainer() instanceof org.eclipse.uml2.uml.Class && UMLUtil.getStereotypeApplication((Element) propertyToEdit.eContainer(), ConstraintBlock.class) != null) {
if (UMLUtil.getStereotypeApplication(propertyToEdit, ConstraintProperty.class) == null) {
return gmfCommand;
}
}
Association relatedAssociation = propertyToEdit.getAssociation();
// The edited property has to be related to a SysML association
if ((relatedAssociation == null) || !(ElementUtil.hasNature(relatedAssociation, SysMLElementTypes.SYSML_NATURE))) {
// If no association exist and the new type is a Block
// (not a ConstraintBlock => a property typed by a ConstraintBlock is a ConstraintProperty, not a Part neither a Reference),
// add the association
if ((relatedAssociation == null) && ((ISpecializationType) SysMLElementTypes.BLOCK).getMatcher().matches((Type) request.getValue()) && !((ISpecializationType) SysMLElementTypes.CONSTRAINT_BLOCK).getMatcher().matches((Type) request.getValue())
&& ((ISpecializationType) SysMLElementTypes.BLOCK).getMatcher().matches(propertyToEdit.eContainer())) {
ICommand addAssociationCommand = getCreatePartAssociationCommand((org.eclipse.uml2.uml.Class) propertyToEdit.eContainer(), propertyToEdit, (org.eclipse.uml2.uml.Class) request.getValue());
gmfCommand = CompositeCommand.compose(gmfCommand, addAssociationCommand);
}
return gmfCommand;
}
// Setting new type can be related to an association re-orient (or trigger the association re-orient)
// Retrieve elements already under re-factor.
List<EObject> currentlyRefactoredElements = (request.getParameter(RequestParameterConstants.ASSOCIATION_REFACTORED_ELEMENTS) != null) ? (List<EObject>) request.getParameter(RequestParameterConstants.ASSOCIATION_REFACTORED_ELEMENTS)
: new ArrayList<EObject>();
if (!currentlyRefactoredElements.contains(propertyToEdit)) {
currentlyRefactoredElements.add(propertyToEdit);
request.getParameters().put(RequestParameterConstants.ASSOCIATION_REFACTORED_ELEMENTS, currentlyRefactoredElements);
// Current association already under re-factor ?
if (currentlyRefactoredElements.contains(relatedAssociation)) {
return gmfCommand;
}
}
// If the new type is not a block, destroy related association
// This must be done only if the setting of the property type is not part of an association re-orient (hence after the previous code-block),
// otherwise there is no legitimate reason to destroy the existing association while re-orienting it.
if (!((ISpecializationType) SysMLElementTypes.BLOCK).getMatcher().matches((Type) request.getValue()) && propertyToEdit.getType() != null) {
ICommand destroyCommand = getDestroyPartAssociationCommand(relatedAssociation, propertyToEdit);
gmfCommand = CompositeCommand.compose(gmfCommand, destroyCommand);
return gmfCommand;
}
ICommand refactorCommand = getAssociationRefactoringCommand(propertyToEdit, relatedAssociation, request);
gmfCommand = CompositeCommand.compose(gmfCommand, refactorCommand);
}
if (gmfCommand != null) {
gmfCommand = gmfCommand.reduce();
}
return gmfCommand;
}
/**
*
* @see org.eclipse.gmf.runtime.emf.type.core.edithelper.AbstractEditHelperAdvice#getAfterSetCommand(org.eclipse.gmf.runtime.emf.type.core.requests.SetRequest)
*
* @param setRequest
* @return
*/
@Override
protected ICommand getAfterSetCommand(SetRequest setRequest) {
ICommand afterSetCommand = super.getAfterSetCommand(setRequest);
EObject elementToEdit = setRequest.getElementToEdit();
if ((elementToEdit instanceof Property) && !(elementToEdit instanceof Port) && (setRequest.getFeature() == UMLPackage.eINSTANCE.getTypedElement_Type()) && (setRequest.getValue() instanceof Type)) {
afterSetCommand = getDestroyAssociatedNestedConnectorCommand((Property) elementToEdit, afterSetCommand);
}
return afterSetCommand;
}
/**
*
* @see org.eclipse.gmf.runtime.emf.type.core.edithelper.AbstractEditHelperAdvice#getAfterDestroyDependentsCommand(org.eclipse.gmf.runtime.emf.type.core.requests.DestroyDependentsRequest)
*
* @param destroyDependentsRequest
* @return
*/
@Override
protected ICommand getAfterDestroyDependentsCommand(DestroyDependentsRequest destroyDependentsRequest) {
ICommand afterDestroyDependentsCommand = super.getAfterDestroyDependentsCommand(destroyDependentsRequest);
EObject elementToDestroy = destroyDependentsRequest.getElementToDestroy();
if (elementToDestroy instanceof Property) {
afterDestroyDependentsCommand = getDestroyAssociatedNestedConnectorCommand((Property) elementToDestroy, afterDestroyDependentsCommand);
}
return afterDestroyDependentsCommand;
}
/**
* Create a destroy command for all connectors that have this property in their <NestedConnectorEnd> property path.
*
* @param property
* the part to be destroyed
* @param command
* @return
*/
private ICommand getDestroyAssociatedNestedConnectorCommand(Property property, ICommand command) {
// When creating a property in a new-element dialog, it is not attached to the model, yet. So, there will be no need to worry about connectors
Collection<Setting> allReferences = CacheAdapter.getCacheAdapter(property).getInverseReferences(property);
List<Connector> connectorsToDestroy = ConnectorUtils.filterConnectorByPropertyInNestedConnectorEnd(allReferences, property);
for (Connector connector : connectorsToDestroy) {
ICommand destroyConnectorCommand = getDestroyConnectorCommand(connector);
command = CompositeCommand.compose(command, destroyConnectorCommand);
}
return command;
}
/**
* Create a connector destroy command.
*
* @param connector
* @return the connector destroy command
*/
private ICommand getDestroyConnectorCommand(Connector connector) {
DestroyElementRequest request = new DestroyElementRequest(connector, false);
IElementEditService provider = ElementEditServiceUtils.getCommandProvider(connector.eContainer());
if (provider == null) {
return null;
}
return provider.getEditCommand(request);
}
/**
* Create a re-factoring command related to a Property move.
*
* @param setProperty
* the property which type is set
* @param associationToRefactor
* the association to re-factor (re-orient action)
* @param request
* the original set request
* @return the re-factoring command
*/
private ICommand getAssociationRefactoringCommand(Property setProperty, Association associationToRefactor, SetRequest request) {
Association relatedAssociation = setProperty.getAssociation(); // Should not be null, test before calling method.
if (associationToRefactor.getMemberEnds().size() >= 2) {
// Re-orient the related association (do not use edit service to avoid infinite loop here)
int direction = ReorientRequest.REORIENT_TARGET;
if (setProperty == associationToRefactor.getMemberEnds().get(1)) {
direction = ReorientRequest.REORIENT_SOURCE;
}
ReorientRelationshipRequest reorientRequest = new ReorientRelationshipRequest(relatedAssociation, (Type) request.getValue(), setProperty.eContainer(), direction);
reorientRequest.addParameters(request.getParameters());
IElementEditService provider = ElementEditServiceUtils.getCommandProvider(relatedAssociation);
if (provider != null) {
return provider.getEditCommand(reorientRequest);
}
}
return null;
}
/**
* Create a part association creation command.
*
* @return the part association creation command
*/
private ICommand getCreatePartAssociationCommand(final org.eclipse.uml2.uml.Class sourceBlock, final Property sourceProperty, final org.eclipse.uml2.uml.Class targetBlock) {
return new AbstractCommand("Create part association") {
@Override
protected CommandResult doUndoWithResult(IProgressMonitor progressMonitor, IAdaptable info) throws ExecutionException {
// TODO Auto-generated method stub
return null;
}
@Override
protected CommandResult doRedoWithResult(IProgressMonitor progressMonitor, IAdaptable info) throws ExecutionException {
// TODO Auto-generated method stub
return null;
}
@Override
protected CommandResult doExecuteWithResult(IProgressMonitor progressMonitor, IAdaptable info) throws ExecutionException {
Association association = UMLFactory.eINSTANCE.createAssociation();
// Add the association in the model
org.eclipse.uml2.uml.Package container = (org.eclipse.uml2.uml.Package) EMFCoreUtil.getLeastCommonContainer(Arrays.asList(new EObject[] { sourceBlock, targetBlock }), UMLPackage.eINSTANCE.getPackage());
container.getPackagedElements().add(association);
// Use existing Property as source...
association.getMemberEnds().add(sourceProperty);
// ... and create the opposite (unnamed) Property
Property targetProperty = UMLFactory.eINSTANCE.createProperty();
association.getOwnedEnds().add(targetProperty);
// Set Association name
// Initialize the element name based on the created IElementType
String initializedName = NamedElementHelper.getDefaultNameWithIncrementFromBase(UMLPackage.eINSTANCE.getAssociation().getName(), association.eContainer().eContents());
association.setName(initializedName);
// Add SysML Nature on the new Association
ElementUtil.addNature(association, SysMLElementTypes.SYSML_NATURE);
return CommandResult.newOKCommandResult(association);
}
};
}
/**
* Apply/remove the ConstraintProperty stereotype application
*
* @return the ConstraintProperty stereotype application command
*/
private ICommand getConstraintPropertyStereotypeApplicationCommand(final Property sourceProperty, final org.eclipse.uml2.uml.Class targetBlock, final SetRequest request) {
return new AbstractTransactionalCommand(request.getEditingDomain(), "Apply/Remove ConstraintProperty Stereotype", null) {
@Override
protected CommandResult doExecuteWithResult(IProgressMonitor progressMonitor, IAdaptable info) throws ExecutionException {
// SysML specification : all property typed by a ConstraintBlock must have a ContraintProperty stereotype applied
ConstraintProperty constraintPropertyApplication = UMLUtil.getStereotypeApplication(sourceProperty, ConstraintProperty.class);
if (UMLUtil.getStereotypeApplication(targetBlock, ConstraintBlock.class) != null) {
if (constraintPropertyApplication == null) {
StereotypeApplicationHelper.INSTANCE.applyStereotype(sourceProperty, ConstraintsPackage.eINSTANCE.getConstraintProperty());
// Remove representations
Set<View> memberViewsToDestroy = CrossReferencerUtil.getCrossReferencingViews(sourceProperty, null);
if (memberViewsToDestroy.size() != 0) {
final IPreferenceStore store = Activator.getDefault().getPreferenceStore();
final String DISPLAY_MESSAGE_FOR_TYPING_ACTION_PREFERENCE_KEY = "displayMessageForTypingActionPreferenceKey";
boolean contains = store.contains(DISPLAY_MESSAGE_FOR_TYPING_ACTION_PREFERENCE_KEY);
if (!contains) {
store.setValue(DISPLAY_MESSAGE_FOR_TYPING_ACTION_PREFERENCE_KEY, MessageDialogWithToggle.NEVER);
store.setDefault(DISPLAY_MESSAGE_FOR_TYPING_ACTION_PREFERENCE_KEY, MessageDialogWithToggle.NEVER);
}
final String hideValue = store.getString(DISPLAY_MESSAGE_FOR_TYPING_ACTION_PREFERENCE_KEY);
if (!hideValue.equals(MessageDialogWithToggle.ALWAYS)) {
int size = memberViewsToDestroy.size();
MessageDialogWithToggle.openInformation(Display.getDefault().getActiveShell(), "Change Type Action",
"WARNING! Typing a Property by a ConstraintBlock make this property become a ConstraintProperty. ConstraintProperty have a specific representation. "
+ "\nSo all representations of this property will be removed from the model (" + size + " occurence" + ((size > 1) ? "s" : "") + ").",
"Don't show this dialog the next time", false, store,
DISPLAY_MESSAGE_FOR_TYPING_ACTION_PREFERENCE_KEY);
}
}
for (View view : memberViewsToDestroy) {
final DestroyElementRequest destroyRequest = new DestroyElementRequest(request.getEditingDomain(), view, false);
final IElementEditService commandProvider = ElementEditServiceUtils.getCommandProvider(view);
ICommand editCommand = commandProvider.getEditCommand(destroyRequest);
editCommand.execute(progressMonitor, info);
}
}
} else {
if (constraintPropertyApplication != null) {
StereotypeApplicationHelper.INSTANCE.removeFromContainmentList(sourceProperty, constraintPropertyApplication);
}
}
return CommandResult.newOKCommandResult(sourceProperty);
}
};
}
/**
* Create a part association destroy command.
*
* @return the part association destroy command
*/
@SuppressWarnings("unchecked")
private ICommand getDestroyPartAssociationCommand(Association partAssociation, Property propertyToEdit) {
DestroyElementRequest request = new DestroyElementRequest(partAssociation, false);
List<EObject> dependentsToKeep = Arrays.asList(new EObject[] { propertyToEdit });
request.getParameters().put(RequestParameterConstants.DEPENDENTS_TO_KEEP, dependentsToKeep);
IElementEditService provider = ElementEditServiceUtils.getCommandProvider(partAssociation.eContainer());
if (provider == null) {
return null;
}
ICommand destroyCommand = provider.getEditCommand(request);
return destroyCommand;
}
}