/***************************************************************************** * 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: * Emilien Perico (Atos Origin) emilien.perico@atosorigin.com - Initial API and implementation * *****************************************************************************/ package org.eclipse.papyrus.uml.diagram.activity.helper; import java.util.Collection; import java.util.HashSet; import java.util.Set; import org.eclipse.core.runtime.IStatus; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature.Setting; import org.eclipse.emf.ecore.impl.ENotificationImpl; import org.eclipse.emf.edit.command.AddCommand; import org.eclipse.emf.edit.command.RemoveCommand; import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.emf.validation.AbstractModelConstraint; import org.eclipse.emf.validation.EMFEventType; import org.eclipse.emf.validation.IValidationContext; import org.eclipse.gmf.runtime.common.core.util.Log; import org.eclipse.gmf.runtime.diagram.ui.internal.DiagramUIPlugin; import org.eclipse.gmf.runtime.diagram.ui.internal.DiagramUIStatusCodes; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.osgi.util.NLS; import org.eclipse.papyrus.infra.core.utils.EditorUtils; import org.eclipse.papyrus.uml.diagram.activity.edit.dialogs.ConfirmActivityParameterNodeAndParameterSyncDialog; import org.eclipse.papyrus.uml.diagram.activity.edit.dialogs.WarningAndLinkDialog; import org.eclipse.papyrus.uml.diagram.activity.part.Messages; import org.eclipse.papyrus.uml.diagram.activity.part.UMLDiagramEditorPlugin; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.uml2.common.util.CacheAdapter; import org.eclipse.uml2.uml.Activity; import org.eclipse.uml2.uml.ActivityParameterNode; import org.eclipse.uml2.uml.NamedElement; import org.eclipse.uml2.uml.Parameter; import org.eclipse.uml2.uml.UMLFactory; import org.eclipse.uml2.uml.UMLPackage; /** * ActivityParameterAndParameterSynchronizer is a validator (see corresponding * extensions) to synchronize ActivityParameterNode with its Parameter * */ public class ActivityParameterAndParameterSynchronizer extends AbstractModelConstraint { /** The label provider */ private static final ILabelProvider labelProvider = new AdapterFactoryLabelProvider(UMLDiagramEditorPlugin.getInstance().getItemProvidersAdapterFactory()); /** The activity parameter nodes to remove with parameter */ private Set<ActivityParameterNode> nodesToRemove = new HashSet<ActivityParameterNode>(); /** * {@inheritDoc} */ @SuppressWarnings("restriction") @Override public IStatus validate(IValidationContext ctx) { try { EObject eObject = ctx.getTarget(); if(eObject instanceof Activity) { return handleActivityModification((Activity)eObject, ctx); } else if(eObject instanceof ActivityParameterNode) { return handleActivityParameterNodeModification((ActivityParameterNode)eObject, ctx); } else if(eObject instanceof Parameter) { return handleParameterModification((Parameter)eObject, ctx); } return ctx.createSuccessStatus(); } catch (RuntimeException rte) { // avoid throwing uncaught exception which would disable the // constraint Log.warning(DiagramUIPlugin.getInstance(), DiagramUIStatusCodes.IGNORED_EXCEPTION_WARNING, "Unexpected exception during Activity Parameter Node and Parameter synchronization : ", rte); // ensure that the constraint's failure does not prevent // modification return ctx.createSuccessStatus(); } } /** * Handle activity parameter node modification to forbid activity parameter * node type modification * * @param eObject * @param ctx * @return the status */ private IStatus handleActivityParameterNodeModification(ActivityParameterNode eObject, IValidationContext ctx) { if(EMFEventType.SET.equals(ctx.getEventType()) && UMLPackage.eINSTANCE.getTypedElement_Type().equals(ctx.getFeature())) { // does not allow type change for activity parameter node, display a // message to inform the user final Parameter parameter = eObject.getParameter(); if(parameter != null) { final String elementLabel = labelProvider.getText(parameter); final String message = NLS.bind(Messages.ActivityParameterAndParameterSynchronizer_UnauthorizedModificationRedirection, elementLabel); SafeDialogOpenerDuringValidation<Void> opener = new SafeDialogOpenerDuringValidation<Void>() { protected Void openDialog() { WarningAndLinkDialog dialog = new WarningAndLinkDialog(new Shell(Display.getDefault()), Messages.ActivityParameterAndParameterSynchronizer_UnauthorizedModificationTitle, message, parameter, elementLabel); dialog.open(); return null; } }; opener.execute(); return ctx.createFailureStatus(); } } return ctx.createSuccessStatus(); } /** * Handle parameter modification to synchronize the type of the associated * activity parameter nodes * * @param eObject * @param ctx * @return the status */ private IStatus handleParameterModification(Parameter eObject, IValidationContext ctx) { if(EMFEventType.SET.equals(ctx.getEventType()) && UMLPackage.eINSTANCE.getTypedElement_Type().equals(ctx.getFeature())) { // change the type of all the associated activity parameter node for(ActivityParameterNode node : getActivityParameterNodesFromParameter(eObject)) { node.setType(node.getParameter().getType()); } } else if(EMFEventType.SET.equals(ctx.getEventType()) && UMLPackage.eINSTANCE.getNamedElement_Name().equals(ctx.getFeature())) { // set the name of all the unnamed associated activity parameter // nodes for(ActivityParameterNode node : getActivityParameterNodesFromParameter(eObject)) { if(node.getName() == null || "".equals(node.getName())) { node.setName(eObject.getName()); } } } return ctx.createSuccessStatus(); } /** * Handle activity modification - add parameter node: set the type with the * associated parameter type - remove parameter: remove all the associated * activity parameter nodes * * @param eObject * @param ctx * @return the status */ private IStatus handleActivityModification(Activity eObject, IValidationContext ctx) { // initialize type when an activity parameter node is created if((EMFEventType.ADD.equals(ctx.getEventType()) || EMFEventType.ADD_MANY.equals(ctx.getEventType())) && ctx.getFeatureNewValue() instanceof ActivityParameterNode) { ActivityParameterNode activityParameterNode = (ActivityParameterNode)ctx.getFeatureNewValue(); // The type of an activity parameter node is the same as the type of // its parameter. activityParameterNode.setType(activityParameterNode.getParameter().getType()); } // constraint: the nodes of an activity must include one // ActivityParameterNode for each parameter if((EMFEventType.ADD.equals(ctx.getEventType()) || EMFEventType.ADD_MANY.equals(ctx.getEventType())) && ctx.getFeatureNewValue() instanceof Parameter) { Parameter parameter = (Parameter)ctx.getFeatureNewValue(); if(getActivityParameterNodesFromParameter(parameter).isEmpty()) { ActivityParameterNode apn = UMLFactory.eINSTANCE.createActivityParameterNode(); apn.setParameter(parameter); apn.setName(parameter.getName()); apn.setType(parameter.getType()); Command cmd = getAddActivityParameterNodesCmd(eObject, apn); if(cmd.canExecute()) { cmd.execute(); } else { return ctx.createFailureStatus(); } } } // parameter deletion else if(((EMFEventType.REMOVE.equals(ctx.getEventType()) && ctx.getFeatureNewValue() instanceof Parameter) || EMFEventType.REMOVE_MANY.equals(ctx.getEventType()))) { nodesToRemove.clear(); // remove associated activity parameter nodes with the parameter for(Notification n : ctx.getAllEvents()) { if(ENotificationImpl.SET == n.getEventType() && UMLPackage.eINSTANCE.getActivityParameterNode_Parameter().equals(n.getFeature()) && n.getNotifier() instanceof ActivityParameterNode) { nodesToRemove.add((ActivityParameterNode)n.getNotifier()); } // first request is to remove the activity parameter node, it // must not be remove because of parameter deletion if(ENotificationImpl.REMOVE == n.getEventType() && n.getOldValue() instanceof ActivityParameterNode) { nodesToRemove.remove(n.getOldValue()); } } if(!nodesToRemove.isEmpty()) { if(askForValidation(nodesToRemove)) { Command cmd = getRemoveActivityParameterNodesCmd(eObject, nodesToRemove); if(cmd.canExecute()) { cmd.execute(); } else { return ctx.createFailureStatus(); } } else { return ctx.createFailureStatus(); } } } return ctx.createSuccessStatus(); } /** * Gets the associated activity parameter nodes from a specified parameter. * * @param parameter * the parameter * @return the activity parameter nodes */ private Set<ActivityParameterNode> getActivityParameterNodesFromParameter(Parameter parameter) { Set<ActivityParameterNode> references = new HashSet<ActivityParameterNode>(); if(parameter != null) { Collection<Setting> inverseReferences = CacheAdapter.INSTANCE.getNonNavigableInverseReferences(parameter); for(Setting ref : inverseReferences) { if(UMLPackage.eINSTANCE.getActivityParameterNode_Parameter().equals(ref.getEStructuralFeature()) && ref.getEObject().eContainer() != null) { references.add((ActivityParameterNode)ref.getEObject()); } } } return references; } /** * Command to remove the activity parameter nodes that's not have associated * parameter. * * @param owner * the activity that owns the nodes * @param nodes * the nodes * @return the remove command */ private Command getRemoveActivityParameterNodesCmd(Activity owner, Set<ActivityParameterNode> nodes) { TransactionalEditingDomain editingdomain = EditorUtils.getTransactionalEditingDomain(); return RemoveCommand.create(editingdomain, owner, UMLPackage.eINSTANCE.getActivity_Node(), nodes); } /** * Gets the adds the activity parameter nodes command. * * @param owner * the owner * @param node * the node * @return the adds the activity parameter nodes command */ private Command getAddActivityParameterNodesCmd(Activity owner, ActivityParameterNode node) { TransactionalEditingDomain editingdomain = EditorUtils.getTransactionalEditingDomain(); return AddCommand.create(editingdomain, owner, UMLPackage.eINSTANCE.getActivity_Node(), node); } /** * Ask the user to validate all the implied modifications (parameters and * activity parameter nodes) * * @param parameterNodes * the list of impacted activityParameterNodes * @return whether the user validates the modifications */ protected boolean askForValidation(final Set<? extends NamedElement> parameterNodes) { SafeDialogOpenerDuringValidation<Boolean> opener = new SafeDialogOpenerDuringValidation<Boolean>() { protected Boolean openDialog() { return ConfirmActivityParameterNodeAndParameterSyncDialog.openConfirmFromParameter(Display.getDefault().getActiveShell(), parameterNodes, labelProvider); } }; return opener.execute(); } }