Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 316f82fe41107fde6558f8f1741d81767459665d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
/*****************************************************************************
 * Copyright (c) 2011-2012 CEA LIST.
 *
 * 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
 *
 *****************************************************************************/
package org.eclipse.papyrus.sysml.service.types.helper.advice;

import java.util.ArrayList;
import java.util.Arrays;
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.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.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.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) {
		List<Connector> instancesFilteredByType = org.eclipse.papyrus.uml.tools.utils.ElementUtil.getInstancesFilteredByType(property.getModel(), Connector.class, null);
		List<Connector> connectorToDestroy = ConnectorUtils.filterConnectorByPropertyInNestedConnectorEnd(instancesFilteredByType, (Property)property);
		for(Connector connector : connectorToDestroy) {
			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 = ReorientRelationshipRequest.REORIENT_TARGET;
			if(setProperty == associationToRefactor.getMemberEnds().get(1)) {
				direction = ReorientRelationshipRequest.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());
						final String diagramType = null;
						// Remove representations
						Set<View> memberViewsToDestroy = CrossReferencerUtil.getCrossReferencingViews(sourceProperty, diagramType);
						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;
	}
}

Back to the top