/***************************************************************************** * Copyright (c) 2009 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: * Patrick Tessier (CEA LIST) patrick.tessier@cea.fr - Initial API and implementation * Remi Schnekenburger (CEA LIST) remi.schnekenburger@cea.fr - additional features * *****************************************************************************/ package org.eclipse.papyrus.uml.diagram.sequence.edit.policies; import static org.eclipse.papyrus.uml.diagram.common.Activator.log; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.transaction.Transaction; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.emf.workspace.AbstractEMFOperation; import org.eclipse.gef.EditPart; import org.eclipse.gef.EditPartViewer; import org.eclipse.gef.commands.Command; import org.eclipse.gef.commands.UnexecutableCommand; import org.eclipse.gef.editpolicies.AbstractEditPolicy; import org.eclipse.gmf.runtime.common.core.command.ICommand; import org.eclipse.gmf.runtime.common.core.util.StringStatics; import org.eclipse.gmf.runtime.diagram.core.commands.DeleteCommand; import org.eclipse.gmf.runtime.diagram.core.listener.DiagramEventBroker; import org.eclipse.gmf.runtime.diagram.core.listener.NotificationListener; import org.eclipse.gmf.runtime.diagram.ui.commands.ICommandProxy; import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart; import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramGraphicalViewer; import org.eclipse.gmf.runtime.diagram.ui.util.EditPartUtil; import org.eclipse.gmf.runtime.emf.type.core.requests.DestroyElementRequest; import org.eclipse.gmf.runtime.notation.View; import org.eclipse.papyrus.infra.core.listenerservice.IPapyrusListener; import org.eclipse.papyrus.infra.services.edit.service.ElementEditServiceUtils; import org.eclipse.papyrus.infra.services.edit.service.IElementEditService; import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.DurationConstraintEditPart; import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.LifelineEditPart; import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.TimeConstraintEditPart; import org.eclipse.papyrus.uml.diagram.sequence.edit.parts.TimeObservationEditPart; import org.eclipse.papyrus.uml.diagram.sequence.util.SequenceUtil; import org.eclipse.uml2.uml.DurationConstraint; import org.eclipse.uml2.uml.DurationObservation; import org.eclipse.uml2.uml.Element; import org.eclipse.uml2.uml.NamedElement; import org.eclipse.uml2.uml.OccurrenceSpecification; import org.eclipse.uml2.uml.TimeConstraint; import org.eclipse.uml2.uml.TimeObservation; /** * Edit Policy in charge of the removal of time/duration constraint/observation which no longer have associated events. * <P> * This view checks that the host edit part, a time/duration constraint/observation edit part, has necessary associated events. It listens for model * notifications. As soon as it receives a remove event, it checks whether the time element should be also deleted.<BR/> * </P> */ public class DeleteTimeElementWithoutEventPolicy extends AbstractEditPolicy implements NotificationListener, IPapyrusListener { /** The key to install this edit policy */ public static final String KEY = "DeleteTimeElementWithoutEvent"; /** list of element to listen */ protected HashMap<EObject, List<View>> additionalParentToListen = new HashMap<EObject, List<View>>(); /** stores the host associated semantic element */ protected EObject hostSemanticElement; /** * Adds additional listeners to the diagram event broker. */ @Override public void activate() { // retrieve the view and the element associated to the host edit part final View hostView = (View)getHost().getModel(); hostSemanticElement = hostView.getElement(); // adds listener to the event broker, listening for the view and the semantic element associated to the host edit part getDiagramEventBroker().addNotificationListener(hostView, this); getDiagramEventBroker().addNotificationListener(hostSemanticElement, this); // retrieve the list of linked view to listen parents for(View linkedView : getLinkedViews()) { getDiagramEventBroker().addNotificationListener(linkedView.eContainer(), this); } super.activate(); } /** * Removes this edit policy as listener for changes to the specified semanticParent * * @param semanticParent * the semantic parent to stop listen * @param childView * the view that does not requires this additional listener */ protected void removeAdditionalParentToListen(EObject semanticParent, View childView) { // removes the view from the list of views that requires a listener for the semantic parent if(additionalParentToListen.containsKey(semanticParent)) { List<View> views = additionalParentToListen.get(semanticParent); assert (views != null) : "list should not be null"; views.remove(childView); if(views.isEmpty()) { additionalParentToListen.remove(semanticParent); // check this is not the parent semantic element of the host's semantic element if(!semanticParent.equals(((View)getHost().getModel()).getElement())) { getDiagramEventBroker().removeNotificationListener(semanticParent, this); } } } } /** * {@inheritDoc} */ @Override public void deactivate() { // retrieve the view and the element associated to the host edit part final View hostView = (View)getHost().getModel(); // removes all notification listeners for the additional parent to listen for(EObject parent : additionalParentToListen.keySet()) { getDiagramEventBroker().removeNotificationListener(parent, this); } additionalParentToListen.clear(); additionalParentToListen = null; getDiagramEventBroker().removeNotificationListener(hostView, this); getDiagramEventBroker().removeNotificationListener(hostSemanticElement, this); // removes the reference to the semantic element hostSemanticElement = null; super.deactivate(); } /** * Deletes the time element. */ protected final void deleteTimeElement() { Command cmd = getDeleteElementCommand(false); if(cmd.canExecute()) { executeCommand(cmd); } } /** * Deletes a time element view. */ protected final void deleteTimeView() { Command cmd = getDeleteElementCommand(true); if(cmd.canExecute()) { executeCommand(cmd); } } /** * Executes the supplied command inside an <code>unchecked action</code> * * @param cmd * command that can be executed (i.e., cmd.canExecute() == true) */ protected void executeCommand(final Command cmd) { Map<String, Boolean> options = null; EditPart ep = getHost(); boolean isActivating = true; // use the viewer to determine if we are still initializing the diagram // do not use the DiagramEditPart.isActivating since ConnectionEditPart's // parent will not be a diagram edit part EditPartViewer viewer = ep.getViewer(); if(viewer instanceof DiagramGraphicalViewer) { isActivating = ((DiagramGraphicalViewer)viewer).isInitializing(); } if(isActivating || !EditPartUtil.isWriteTransactionInProgress((IGraphicalEditPart)getHost(), false, false)) options = Collections.singletonMap(Transaction.OPTION_UNPROTECTED, Boolean.TRUE); AbstractEMFOperation operation = new AbstractEMFOperation(((IGraphicalEditPart)getHost()).getEditingDomain(), StringStatics.BLANK, options) { protected IStatus doExecute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { ((IGraphicalEditPart)getHost()).getDiagramEditDomain().getDiagramCommandStack().execute(cmd); return Status.OK_STATUS; } @Override protected IStatus doUndo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { cmd.undo(); return Status.OK_STATUS; } }; try { operation.execute(new NullProgressMonitor(), null); } catch (ExecutionException e) { log.error(e); } } /** * Check if time element is correctly defined * * @return true if it has necessary information, false if it is to be deleted */ protected boolean isTimeElementDefined() { if(hostSemanticElement instanceof TimeObservation) { return ((TimeObservation)hostSemanticElement).getEvent() != null; } else if(hostSemanticElement instanceof DurationObservation) { return ((DurationObservation)hostSemanticElement).getEvents().size() >= 2; } else if(hostSemanticElement instanceof TimeConstraint) { return ((TimeConstraint)hostSemanticElement).getConstrainedElements().size() > 0; } else if(hostSemanticElement instanceof DurationConstraint) { /* * Note that DurationConstraint may have only one ConstrainedElement. * But in such a case, we suppose it has not been created as one of the concerned edit parts. */ return ((DurationConstraint)hostSemanticElement).getConstrainedElements().size() >= 2; } return true; } /** * Get the list of other required figures * * @return list of views */ protected List<View> getLinkedViews() { if(getHost() instanceof TimeObservationEditPart && hostSemanticElement instanceof TimeObservation) { LifelineEditPart lifeline = SequenceUtil.getParentLifelinePart(getHost()); NamedElement occ = ((TimeObservation)hostSemanticElement).getEvent(); if(occ instanceof OccurrenceSpecification) { EditPart part = SequenceUtil.getLinkedEditPart(lifeline, (OccurrenceSpecification)occ); if(part != null) { return Collections.singletonList((View)part.getModel()); } } return Collections.emptyList(); } else if(getHost() instanceof TimeConstraintEditPart && hostSemanticElement instanceof TimeConstraint) { LifelineEditPart lifeline = SequenceUtil.getParentLifelinePart(getHost()); List<Element> occs = ((TimeConstraint)hostSemanticElement).getConstrainedElements(); if(occs.size() > 0 & occs.get(0) instanceof OccurrenceSpecification) { EditPart part = SequenceUtil.getLinkedEditPart(lifeline, (OccurrenceSpecification)occs.get(0)); if(part != null) { return Collections.singletonList((View)part.getModel()); } } return Collections.emptyList(); } else if(getHost() instanceof DurationConstraintEditPart && hostSemanticElement instanceof TimeConstraint) { LifelineEditPart lifeline = SequenceUtil.getParentLifelinePart(getHost()); List<Element> occs = ((TimeConstraint)hostSemanticElement).getConstrainedElements(); if(occs.size() >= 2 && occs.get(0) instanceof OccurrenceSpecification && occs.get(1) instanceof OccurrenceSpecification) { EditPart part1 = SequenceUtil.getLinkedEditPart(lifeline, (OccurrenceSpecification)occs.get(0)); EditPart part2 = SequenceUtil.getLinkedEditPart(lifeline, (OccurrenceSpecification)occs.get(1)); List<View> list = new ArrayList<View>(2); if(part1 != null) { list.add((View)part1.getModel()); } if(part2 != null) { list.add((View)part2.getModel()); } return list; } return Collections.emptyList(); } // a label on a message always has its parent message return Collections.emptyList(); } /** * Check if time element has required other figures * * @return true if the time element figure miss one of the figure representing its ends. */ protected boolean timeElementMissAnEventFigure() { if(getHost() instanceof TimeObservationEditPart && hostSemanticElement instanceof TimeObservation) { LifelineEditPart lifeline = SequenceUtil.getParentLifelinePart(getHost()); NamedElement occ = ((TimeObservation)hostSemanticElement).getEvent(); if(occ instanceof OccurrenceSpecification) { return SequenceUtil.getLinkedEditPart(lifeline, (OccurrenceSpecification)occ) == null; } return true; } else if(getHost() instanceof TimeConstraintEditPart && hostSemanticElement instanceof TimeConstraint) { LifelineEditPart lifeline = SequenceUtil.getParentLifelinePart(getHost()); List<Element> occs = ((TimeConstraint)hostSemanticElement).getConstrainedElements(); if(occs.size() > 0 && occs.get(0) instanceof OccurrenceSpecification) { return SequenceUtil.getLinkedEditPart(lifeline, (OccurrenceSpecification)occs.get(0)) == null; } return true; } else if(getHost() instanceof DurationConstraintEditPart && hostSemanticElement instanceof DurationConstraint) { LifelineEditPart lifeline = SequenceUtil.getParentLifelinePart(getHost()); List<Element> occs = ((DurationConstraint)hostSemanticElement).getConstrainedElements(); if(occs.size() >= 2 && occs.get(0) instanceof OccurrenceSpecification && occs.get(1) instanceof OccurrenceSpecification) { return SequenceUtil.getLinkedEditPart(lifeline, (OccurrenceSpecification)occs.get(0)) == null || SequenceUtil.getLinkedEditPart(lifeline, (OccurrenceSpecification)occs.get(1)) == null; } return true; } // a label on a message always has its parent message return false; } /** * Returns a {@link Command} to delete the host element * * @param graphOnly * true if the view only must be removed * @return the command that destroys the host element */ protected Command getDeleteElementCommand(boolean graphOnly) { if(graphOnly) { TransactionalEditingDomain editingDomain = ((IGraphicalEditPart)getHost()).getEditingDomain(); return new ICommandProxy(new DeleteCommand(editingDomain, (View)getHost().getModel())); } else { DestroyElementRequest req = new DestroyElementRequest(hostSemanticElement, false); return getDestroyElementCommand(req); } } /** * Copied from a generated method from a semantic edit policy which supports the edit element service * @param req the DestroyElementRequest * @return the destroy command */ protected Command getDestroyElementCommand(DestroyElementRequest req) { EObject selectedEObject = req.getElementToDestroy(); IElementEditService provider = ElementEditServiceUtils.getCommandProvider(selectedEObject); if(provider != null) { // Retrieve delete command from the Element Edit service ICommand deleteCommand = provider.getEditCommand(req); if(deleteCommand != null) { return new ICommandProxy(deleteCommand); } } return UnexecutableCommand.INSTANCE; } /** * Gets the diagram event broker from the editing domain. * * @return the diagram event broker */ protected DiagramEventBroker getDiagramEventBroker() { TransactionalEditingDomain theEditingDomain = ((IGraphicalEditPart)getHost()).getEditingDomain(); if(theEditingDomain != null) { return DiagramEventBroker.getInstance(theEditingDomain); } return null; } /** * {@inheritDoc} */ public void notifyChanged(Notification notification) { Object notifier = notification.getNotifier(); if(notifier.equals(hostSemanticElement)) { if(Notification.REMOVE == notification.getEventType() || Notification.SET == notification.getEventType()) { deleteTimeElementIfNeeded(); } } else if(notifier instanceof View) { // a view has been modified if(Notification.REMOVE == notification.getEventType()) { deleteTimeElementIfNeeded(); } } } /** * Removes a listener for the specified view, if it exists. * * @param oldView * the old view to check */ protected void removeListenerForView(View oldView) { // create a temp list of elements to delete (iterator concurrent modification..) Map<EObject, List<View>> parentsToDelete = new HashMap<EObject, List<View>>(); for(EObject parent : additionalParentToListen.keySet()) { List<View> parentViews = additionalParentToListen.get(parent); if(parentViews.contains(oldView)) { List<View> views = parentsToDelete.get(parent); if(views == null) { views = new ArrayList<View>(); } views.add(oldView); parentsToDelete.put(parent, views); } } } /** * Updates the listeners for the specified semantic parent */ protected void removeListeners(List<View> impactedViews) { // create a temp list of elements to delete (iterator concurrent modification..) Map<EObject, List<View>> parentsToDelete = new HashMap<EObject, List<View>>(); // collect the elements to delete for(View view : impactedViews) { for(EObject parent : additionalParentToListen.keySet()) { List<View> parentViews = additionalParentToListen.get(parent); if(parentViews.contains(view)) { List<View> views = parentsToDelete.get(parent); if(views == null) { views = new ArrayList<View>(); } views.add(view); parentsToDelete.put(parent, views); } } } // do the job for(EObject object : parentsToDelete.keySet()) { List<View> views = parentsToDelete.get(object); for(View view : views) { removeAdditionalParentToListen(object, view); } } } /** * Check if the time element should be deleted and delete it if necessary. */ protected void deleteTimeElementIfNeeded() { if(!isTimeElementDefined()) { // delete the time element deleteTimeElement(); } if(timeElementMissAnEventFigure()) { // delete the view deleteTimeView(); } } /** * launch a weak synchronization. It could be useful in order to clean a diagram by an external tool. */ public void forceRefresh() { deleteTimeElementIfNeeded(); } }